Errors API Reference¶
All exceptions raised by HyperHTTP inherit from
hyperhttp.HyperHTTPError. They're re-exported at the top level — import them
straight from hyperhttp:
from hyperhttp import (
HyperHTTPError,
TransportError,
ConnectError,
TLSError,
ProtocolError,
RemoteProtocolError,
ReadError,
WriteError,
DNSError,
ProxyError,
TimeoutException,
ConnectTimeout,
ReadTimeout,
WriteTimeout,
PoolTimeout,
PoolClosed,
HTTPStatusError,
InvalidURL,
TooManyRedirects,
CircuitBreakerOpen,
StreamError,
StreamConsumed,
ResponseClosed,
)
Hierarchy¶
HyperHTTPError
├── TransportError
│ ├── ConnectError # TCP connect failed
│ │ └── ConnectTimeout # also TimeoutException
│ ├── TLSError # TLS handshake / cert validation
│ ├── ProtocolError # framing / parser violation
│ │ ├── RemoteProtocolError
│ │ └── LocalProtocolError
│ ├── ReadError # socket read failed / unexpected EOF
│ │ └── ReadTimeout # also TimeoutException
│ ├── WriteError # socket write failed
│ │ └── WriteTimeout # also TimeoutException
│ ├── DNSError # name resolution failed
│ └── ProxyError # HTTP proxy refused or misbehaved
├── TimeoutException
│ ├── ConnectTimeout # (also ConnectError)
│ ├── ReadTimeout # (also ReadError)
│ ├── WriteTimeout # (also WriteError)
│ └── PoolTimeout # waited too long for a pool slot
├── HTTPStatusError # raised by Response.raise_for_status()
├── InvalidURL # URL couldn't be parsed / is unsuitable
├── TooManyRedirects
├── PoolClosed # request in flight when Client.aclose() ran
├── CircuitBreakerOpen # host is currently tripped
└── StreamError
├── StreamConsumed # tried to re-read a consumed body
└── ResponseClosed # operation on a closed response
Noteworthy exceptions¶
HTTPStatusError¶
Raised by Response.raise_for_status() for any 4xx or 5xx response. It
carries the response object:
try:
response = await client.get("https://api.example.com/missing")
response.raise_for_status()
except hyperhttp.HTTPStatusError as e:
print(e.response.status_code)
print(e.response.headers)
await e.response.aread()
print(e.response.text)
CircuitBreakerOpen¶
Raised when a host has been marked unhealthy by the circuit breaker.
except hyperhttp.CircuitBreakerOpen as e:
print(e.host) # e.g. "api.example.com:443"
print(e.remaining) # seconds until the breaker enters HALF-OPEN
ConnectTimeout vs. ReadTimeout¶
These are distinct phases. ConnectTimeout means the TCP connect exceeded
connect_timeout. ReadTimeout means the server accepted the connection but
didn't produce a response within the read phase of the timeout.
RetryError¶
Not re-exported at the top level — import from
hyperhttp.errors.retry if you need to match on it:
from hyperhttp.errors.retry import RetryError
try:
await client.get("https://flaky.example.com/thing")
except RetryError as e:
print(e.original_exception)
print(e.retry_state.attempt_count)
RetryError is only raised once at least one retry has been attempted. The
first failure always surfaces the original typed exception.
Error classification¶
Errors are mapped to categories for retry / circuit-breaker decisions. The defaults:
| Category | Exceptions |
|---|---|
CONNECTION |
ConnectError, DNSError, ProxyError |
TIMEOUT |
ConnectTimeout, ReadTimeout, WriteTimeout, PoolTimeout |
TRANSIENT |
ReadError, WriteError, ProtocolError |
SERVER |
HTTPStatusError with 5xx status |
RATE_LIMIT |
HTTPStatusError with 429 |
FATAL |
InvalidURL, TooManyRedirects, 4xx status (other than 429) |
Pass retry_categories=[...] to RetryPolicy to change which ones are
retried. See the Retry Policy page.
Patterns¶
Catch the broad base class¶
try:
response = await client.get(url)
response.raise_for_status()
except hyperhttp.HyperHTTPError as e:
logger.warning("request failed: %s", e)
Handle timeouts distinctly from other transport failures¶
try:
response = await client.get(url)
except hyperhttp.TimeoutException:
# connect/read/write/pool — any phase
...
except hyperhttp.TransportError:
# TCP, TLS, DNS, framing
...
Drain the response before re-raising¶
If you raise_for_status() and want to log the body, read it first: