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 isrequests.ConnectionError, not Python's built-inConnectionError.Timeout: with subclassesConnectTimeoutandReadTimeout.HTTPError: raised byraise_for_status.TooManyRedirects,InvalidURL,MissingSchema(you forgothttps://),SSLError(a subclass ofConnectionError).JSONDecodeErrorfromr.json().
Catch the most specific exception you can handle and let the rest propagate. A common structure:
- catch
TimeoutandConnectionErrorto retry or report "service unreachable", - catch
HTTPErrorand branch onerr.response.status_code(404 means not found, 401 means re-authenticate, 5xx might be retried), - catch
RequestExceptionlast 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)) # TrueRemember
- 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.