Correct error handling separates 'the server said no' from 'we never got an answer', which need different reactions.
All HTTPX request errors derive from httpx.HTTPError, which has two main branches:
httpx.RequestError: something went wrong while sending or receiving, so there is no complete response. Subclasses includeTransportErrorwithTimeoutException(ConnectTimeout,ReadTimeout,WriteTimeout,PoolTimeout),NetworkError(ConnectError,ReadError,WriteError,CloseError),ProtocolError(malformed HTTP),ProxyErrorandUnsupportedProtocol(for example a missinghttps://). AlsoDecodingErrorandTooManyRedirects.httpx.HTTPStatusError: raised only byraise_for_status()for 4xx and 5xx responses.
Every RequestError carries .request, so you can log which URL failed. HTTPStatusError carries both .request and .response.
A typical pattern:
try:
r = client.get(url)
r.raise_for_status()
except httpx.HTTPStatusError as e: ... # server answered with an error
except httpx.RequestError as e: ... # network-level failureSome exceptions live outside this hierarchy because they are programming errors rather than request failures: httpx.InvalidURL, httpx.CookieConflict, and StreamError subclasses such as ResponseNotRead (accessing .content of a streamed response that was not read) or StreamConsumed.
Unlike Requests, HTTPX exceptions do not inherit from OSError, and the names are httpx-specific, so update except clauses when migrating.
Example
import httpx
def check(url):
try:
httpx.get(url, timeout=2.0).raise_for_status()
return "ok"
except httpx.HTTPStatusError as e:
return f"status {e.response.status_code} from {e.request.url.path}"
except httpx.TimeoutException as e:
return f"timeout: {type(e).__name__}"
except httpx.RequestError as e:
return f"request failed: {type(e).__name__}"
print(check("https://httpbin.org/get")) # ok
print(check("https://httpbin.org/status/500")) # status 500 from /status/500
print(check("https://httpbin.org/delay/5")) # timeout: ReadTimeout
print(check("ftp://example.com")) # request failed: UnsupportedProtocol
print(issubclass(httpx.ConnectError, httpx.RequestError)) # TrueRemember
- RequestError: no complete response (timeouts, network, protocol)
- HTTPStatusError: raised by raise_for_status for 4xx/5xx
- Both inherit from httpx.HTTPError
Watch out for
- Catching only HTTPStatusError and letting timeouts crash the program.