HTTP client (outbound)¶
HTTPClient is a typed wrapper over httpx.AsyncClient for calling
external services with retries + exponential backoff, a circuit-breaker,
default timeouts, and X-Request-ID propagation. It's the outbound
counterpart of the HTTP middleware (which handles inbound
traffic). Requires the [http] extra (httpx).
Basic usage¶
The client is safe to share across requests on the same event loop — it
reuses the internal connection pool. Use it as an async context manager (or
keep a singleton in resources.py and close it on the
lifespan).
from typing import Any
from tempest_fastapi_sdk import HTTPClient
client = HTTPClient(base_url="https://api.example.com", timeout=10.0)
async def fetch_user(user_id: str) -> dict[str, Any]:
"""GET /users/{id} on the external service."""
async with client:
response = await client.get(f"/users/{user_id}")
response.raise_for_status()
return response.json()
Methods: get / post / put / patch / delete (plus a generic
request), all forwarding kwargs to httpx (json=, params=, headers=,
...) and returning an httpx.Response.
Retry + backoff + circuit-breaker¶
Pass a RetryPolicy and tune the breaker thresholds at construction:
from tempest_fastapi_sdk import CircuitOpenError, HTTPClient, RetryPolicy
client = HTTPClient(
base_url="https://api.example.com",
timeout=5.0,
retry_policy=RetryPolicy(
max_attempts=3, # 1 try + 2 retries
backoff_initial_seconds=0.5, # 0.5s, 1s, 2s... (exponential)
backoff_max_seconds=8.0, # cap per wait
),
failure_threshold=5, # open the circuit after 5 straight failures
recovery_seconds=30.0, # half-open after 30s
default_headers={"X-Api-Key": "..."},
propagate_request_id=True, # forward the current request's X-Request-ID
)
async def call() -> None:
try:
async with client:
await client.post("/charge", json={"amount": 100})
except CircuitOpenError:
# The circuit is open — don't hammer the downed upstream.
...
- Retry: retried on transient errors (timeouts, 5xx, connection
failures) up to
max_attempts, with exponential backoff capped bybackoff_max_seconds. - Circuit-breaker: after
failure_thresholdconsecutive failures the circuit opens and calls raiseCircuitOpenErrorimmediately (without touching the network) untilrecovery_secondselapse, then it half-opens to probe. - Request-ID: with
propagate_request_id=True, the in-flight request'sX-Request-ID(fromRequestIDMiddleware) is forwarded to the upstream, stitching logs end-to-end.
Keep it as a singleton in resources.py
Build the HTTPClient once (in src/api/dependencies/resources.py),
expose a get_http_client, and close it on the lifespan with
await client.aclose() — so the connection pool is reused across
requests.
Recap¶
HTTPClient= typedhttpx.AsyncClient+ retry/backoff/circuit-breaker + X-Request-ID.[http]extra. Methodsget/post/put/patch/delete/request→httpx.Response.RetryPolicy(max_attempts, backoff_initial_seconds, backoff_max_seconds)controls retries.failure_threshold/recovery_secondscontrol the breaker;CircuitOpenErrorwhen open.- Share a singleton and close it with
aclose()on shutdown.