Error handling¶
Two separate failure classes, never unified:
HTTPResponseError— the server answered with a 4xx/5xx. Carries.statusand.body_start(the first 100 bytes of the body, for a quick look without decoding),.parsed_body, and.request(the request that got this response — method/url/path/host; handy for telling which call failed when several are in flight at once).HTTPTransportError— never got a response at all: aHTTPTimeoutErrororHTTPConnectionErrorsubclass. pyreqwest's own exception types never leak through; they're translated to these at every call site.
from lothc import HTTPClient, HTTPConnectionError, HTTPResponseError, HTTPTimeoutError
async with HTTPClient.build(base_url="https://pokeapi.co/api/v2/") as client:
try:
await client.get("pokemon/does-not-exist")
except HTTPResponseError as e:
print(e.status, e.body_start)
print(e.request.method, e.request.path) # "GET", "/api/v2/pokemon/does-not-exist"
except HTTPTimeoutError:
print("took too long")
except HTTPConnectionError:
print("never reached the server")
Typed error bodies¶
.parsed_body is None unless you pass error_type — every verb takes it, mirroring
response_data_type, and it decodes the 4xx/5xx body onto HTTPResponseError.parsed_body
instead of leaving it None:
from pydantic import BaseModel
class APIError(BaseModel):
type: str
title: str
status: int
async with HTTPClient.build(base_url="https://api.example.com/") as client:
try:
await client.get("items/7", error_type=APIError)
except HTTPResponseError as e:
print(e.parsed_body) # APIError(type=..., title=..., status=...)
A validation error from whichever decode library you picked (pydantic.ValidationError,
msgspec.ValidationError) is never wrapped, on either the success (response_data_type) or
error (error_type) path — it propagates as-is, since choosing that library is opting into its
own exception too.