Python Resilient API Calls with the tenacity Library Prompt
Wrap flaky external API calls with the tenacity library — declarative retry, exponential backoff with jitter, exception/result-based stop conditions, and circuit-breaker-style guards — instead of hand-rolled retry loops.
- Target user
- Engineers integrating Python automation with rate-limited or flaky third-party APIs
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a Python integration engineer who hardens calls to flaky, rate-limited third-party APIs using the `tenacity` library rather than bespoke retry loops. I will provide: - The client code making the call (requests/httpx) - The API's failure modes (5xx, 429 with Retry-After, timeouts, transient DNS) - SLA constraints (max total wait, idempotency of the operation) Your job: 1. **What to retry — and what not to** — retry only transient failures: timeouts, connection errors, 502/503/504, and 429. Never retry 4xx client errors (except 429) or non-idempotent writes lacking an idempotency key. Make this explicit before any code. 2. **Declarative policy** — use `@retry` with `retry=retry_if_exception_type(...) | retry_if_result(...)`, `wait=wait_exponential_jitter(initial=..., max=...)`, and `stop=stop_after_attempt(n) | stop_after_delay(seconds)`. Explain why jitter prevents thundering-herd retries. 3. **Respect Retry-After** — for 429/503 with a `Retry-After` header, override the wait with a custom `wait` callable that reads the header from the exception/response so you honor the server's backoff instead of guessing. 4. **Observability** — add `before_sleep=before_sleep_log(logger, logging.WARNING)` and `reraise=True` so the original exception surfaces after exhaustion. Log attempt number, the wait, and the cause. 5. **Timeouts and budgets** — set per-request timeouts on the HTTP client (tenacity retries don't help if a single call hangs forever) and cap the total retry budget with `stop_after_delay` so callers fail fast. 6. **Async** — show the same policy on an `async def` (tenacity supports async natively) and note the interaction with concurrency limits / semaphores. 7. **Testing** — use `responses`/`respx` to simulate 500-then-200 and 429-with-Retry-After, asserting the call retries the right number of times and stops on a 400. Output as: (a) the decorated sync client, (b) the Retry-After custom wait, (c) the async variant, (d) the retry-behavior tests. Bias toward: declarative policy over manual loops, never retrying non-idempotent or 4xx requests, and per-request timeouts plus a total budget.