httpx client + tenacity retries with backoff, tested with respx: working versions (tested 2026-10-15)
Retry transient HTTP failures (5xx, 429, timeouts) with tenacity around an httpx client, honoring a max attempt count, tested offline with respx.
passed4 passed · 0 failed2026-10-15 13:23 UTC- Status
- passed
- Last passed
- 2026-10-15 13:23 UTC
- Tests
- 4 / 4
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38741369695
The code
Exactly the files that passed. Your agent can pull them with get_recipe("httpx-tenacity-retry") (install), or see them on GitHub.
httpx==0.28.1 tenacity==9.2.1 respx==0.23.1 pytest==9.1.1
import httpx
from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential
def _transient(exc: BaseException) -> bool:
if isinstance(exc, (httpx.TimeoutException, httpx.ConnectError)):
return True
return isinstance(exc, httpx.HTTPStatusError) and (exc.response.status_code == 429 or exc.response.status_code >= 500)
class ApiClient:
def __init__(self, base_url: str, attempts: int = 4, wait_max: float = 8.0):
self.http = httpx.Client(base_url=base_url, timeout=5.0)
self._get = retry(
retry=retry_if_exception(_transient),
stop=stop_after_attempt(attempts),
wait=wait_exponential(multiplier=0.5, max=wait_max),
reraise=True, # surface the real httpx error, not tenacity.RetryError
)(self._get_once)
def _get_once(self, path: str):
r = self.http.get(path)
r.raise_for_status()
return r.json()
def get_json(self, path: str):
return self._get(path)
import httpx
import pytest
import respx
from client import ApiClient
BASE = "https://api.example.test"
def client(attempts=4):
return ApiClient(BASE, attempts=attempts, wait_max=0)
@respx.mock
def test_retries_5xx_then_succeeds():
route = respx.get(f"{BASE}/x").mock(side_effect=[httpx.Response(503), httpx.Response(502), httpx.Response(200, json={"ok": True})])
assert client().get_json("/x") == {"ok": True}
assert route.call_count == 3
@respx.mock
def test_retries_timeouts():
route = respx.get(f"{BASE}/x").mock(side_effect=[httpx.ReadTimeout("slow"), httpx.Response(200, json=[])])
assert client().get_json("/x") == [] and route.call_count == 2
@respx.mock
def test_no_retry_on_404():
route = respx.get(f"{BASE}/x").mock(return_value=httpx.Response(404))
with pytest.raises(httpx.HTTPStatusError):
client().get_json("/x")
assert route.call_count == 1
@respx.mock
def test_gives_up_and_reraises():
route = respx.get(f"{BASE}/x").mock(return_value=httpx.Response(500))
with pytest.raises(httpx.HTTPStatusError):
client(attempts=3).get_json("/x")
assert route.call_count == 3
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
httpx + tenacity async retries honoring Retry-After, tested with respx
Wrap async httpx calls in tenacity AsyncRetrying that retries 429 and 5xx, honors Retry-After, and patches the wait to zero in tests with respx mocks.
httpx async API client + respx mocks
A typed async API client on httpx with retries on 5xx and Pydantic parsing, tested offline with respx route mocks.
httpx OAuth2 client-credentials client with cached tokens and Link pagination
A sync httpx client that fetches and caches an OAuth2 client-credentials token, refreshes it on 401, and follows Link-header pagination, tested offline with respx routes.
Typed httpx client parsing into pydantic v2 models, tested with respx
An async httpx client that retries 5xx and transport errors, validates responses into pydantic v2 models, and is tested offline with respx route mocks.