Errors and raise_for_status

Requests · Practical · 10 min

Robust code must distinguish network failures, timeouts and server errors, and react to each correctly.

Since error statuses do not raise on their own, the idiom is to call r.raise_for_status() right after the request. It does nothing for statuses below 400 and raises requests.HTTPError for 4xx and 5xx. The exception carries the response as err.response, so you can still read the status and error body.

All Requests exceptions inherit from requests.RequestException (itself an OSError). The important subclasses:

  • ConnectionError: DNS failure, refused connection, network down. Note this is requests.ConnectionError, not Python's built-in ConnectionError.
  • Timeout: with subclasses ConnectTimeout and ReadTimeout.
  • HTTPError: raised by raise_for_status.
  • TooManyRedirects, InvalidURL, MissingSchema (you forgot https://), SSLError (a subclass of ConnectionError).
  • JSONDecodeError from r.json().

Catch the most specific exception you can handle and let the rest propagate. A common structure:

  • catch Timeout and ConnectionError to retry or report "service unreachable",
  • catch HTTPError and branch on err.response.status_code (404 means not found, 401 means re-authenticate, 5xx might be retried),
  • catch RequestException last as a fallback.

Avoid bare except Exception around requests: it hides programming errors like typos in your own code. And remember that raise_for_status must be called; forgetting it is the usual reason an error page ends up in r.json().

Example

import requests

def fetch_user(user_id):
    try:
        r = requests.get(f"https://httpbin.org/status/{user_id}", timeout=5)
        r.raise_for_status()
        return "ok"
    except requests.HTTPError as err:
        code = err.response.status_code
        return "not found" if code == 404 else f"server said {code}"
    except (requests.ConnectionError, requests.Timeout) as err:
        return f"unreachable: {type(err).__name__}"

print(fetch_user(200))   # ok
print(fetch_user(404))   # not found
print(fetch_user(503))   # server said 503

try:
    requests.get("httpbin.org/get", timeout=5)
except requests.exceptions.MissingSchema as e:
    print(type(e).__name__)   # MissingSchema
print(issubclass(requests.ConnectTimeout, requests.Timeout))  # True

Remember

  • Call raise_for_status() after requests to turn 4xx/5xx into HTTPError
  • err.response gives access to the failed response
  • Catch specific exceptions; RequestException is the base class

Watch out for

  • Catching Python's built-in ConnectionError instead of requests.ConnectionError.

Check yourself

  1. Which exception does raise_for_status() raise for a 503 response?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: requests.readthedocs.io/en/latest/user/quickstart/#errors-and-exceptions