Skip to content

Should error responses raise an HTTPError by default? #752

@florimondmanca

Description

@florimondmanca

I raised this question on the chat, but I think it's worth a proper issue to discuss options…

Current situation and pain points

Currently, to be notified of an error response we need to either:

  • Check the .status_code manually:
if response.status_code == 404:
    print("Not found")

if response.is_error:
    print("Error")
  • Call .raise_for_status():
response.raise_for_status()  # Raises an HTTPError

This is mostly in line with what Requests does, but I'd argue we can do better.

In particular, there's a very basic use case we could address better: make a request, then handle any 4xx/5xx.

There are mostly two ways to do this currently:

  • Inspect the .status_code:
import httpx

r = httpx.get("https://httpbin.org/status/500")
if r.status_code == 500:
    ...
  • Call .raise_for_status() to get a proper HTTPError:
import httpx

def fetch(url):
    r = httpx.get(url)
    r.raise_for_status()
    return r

try:
    r = fetch("https://httpbin.org/status/500")
except httpx.HTTPError as exc:
    if exc.response.status_code == 500:
        ...

I think we can argue this looks somewhat clunky. It forces the user to add a .raise_for_status() line basically everywhere — probably as a wrapper around an HTTPX call — otherwise they'd start processing responses they think are valid, when they're actually errors.

Possible future situation

What if request methods automatically raised an HTTPError if we get an error response? Sounds sensible?

import httpx

try:
    r = httpx.get("https://httpbin.org/status/500")
except httpx.HTTPError as exc:
    print(exc.response.status_code)
    # Deal with 500...

This would allow users to have to think about error handling, as they'd be getting HTTPError exceptions thrown at them when hosts aren't able to satisfy the request. We can also easily add a "manual mode" without breaking backwards compat after 1.0…

Planning

If we think this is reasonable, the way I'd see us implement this is:

  • Switch to raise an error as the default behavior before 1.0.
  • This would be done via a raise_for_status=True flag, probably on the client constructor at first.
  • Switch the docs to document the new default behavior, and document raise_for_status=False + response.raise_for_status/response.is_error as a more low-level/manual way of handling things.
  • Later on (i.e. after 1.0), we could extend this to allow a Callable[[int], bool] too, allowing to do things like raise_for_status=lambda s: 500 <= s < 600 so that 4xx error responses don't raise an HTTPError. (Sounds fancy, but I can see this feature request turning up at some point.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    user-experienceEnsuring that users have a good experience using the library

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions