HTTP classifiers & Retry-After
See also: DB recipes, Observability recipes, Worker recipes.
This recipe shows two common patterns:
1) exception-based HTTP retries (raise for status) 2) result-based retries with Retry-After support
Exception-based HTTP retries
Use http_retry_after_classifier when your HTTP client raises exceptions that carry
status/headers (e.g., httpx HTTPStatusError).
import httpx
from redress import Policy, Retry
from redress.extras import http_retry_after_classifier
from redress.strategies import decorrelated_jitter, retry_after_or
policy = Policy(
retry=Retry(
classifier=http_retry_after_classifier,
strategy=retry_after_or(decorrelated_jitter(max_s=5.0)),
max_attempts=5,
deadline_s=10.0,
)
)
def fetch(url: str) -> httpx.Response:
with httpx.Client(timeout=3.0) as client:
resp = client.get(url)
resp.raise_for_status() # raises for 4xx/5xx
return resp
response = policy.call(lambda: fetch("https://httpbin.org/status/429"), operation="http_fetch")
Result-based HTTP retries (no exceptions)
Use result_classifier when you want to inspect responses without raising.
This also lets you attach Retry-After metadata explicitly.
import httpx
from redress import Classification, ErrorClass, Policy, Retry
from redress.strategies import decorrelated_jitter, retry_after_or
def classify_result(resp: httpx.Response) -> ErrorClass | Classification | None:
if resp.status_code == 429:
return Classification(klass=ErrorClass.RATE_LIMIT, retry_after_s=1.0)
if resp.status_code >= 500:
return ErrorClass.SERVER_ERROR
return None
policy = Policy(
retry=Retry(
classifier=lambda exc: ErrorClass.UNKNOWN,
result_classifier=classify_result,
strategy=retry_after_or(decorrelated_jitter(max_s=3.0)),
max_attempts=5,
deadline_s=8.0,
)
)
with httpx.Client(timeout=3.0) as client:
response = policy.call(lambda: client.get("https://httpbin.org/status/503"))
Adding a circuit breaker
Circuit breakers use the same classification that drives retries. This example trips the breaker on repeated server errors.
import httpx
from redress import CircuitBreaker, ErrorClass, Policy, Retry, default_classifier
from redress.strategies import decorrelated_jitter
breaker = CircuitBreaker(
failure_threshold=3,
window_s=30.0,
recovery_timeout_s=10.0,
trip_on={ErrorClass.SERVER_ERROR},
)
policy = Policy(
retry=Retry(
classifier=default_classifier,
strategy=decorrelated_jitter(max_s=2.0),
max_attempts=3,
deadline_s=6.0,
),
circuit_breaker=breaker,
)
def fetch(url: str) -> httpx.Response:
with httpx.Client(timeout=3.0) as client:
resp = client.get(url)
resp.raise_for_status()
return resp
response = policy.call(lambda: fetch("https://httpbin.org/status/500"), operation="http_fetch")