Tenacity Python
Treat a retry as a policy around repeated attempts, not as an error-handling
decorator added after a failure. Preserve the operation's idempotency, deadline,
failure taxonomy, and observability.
Boundary
Use this skill when a project uses Tenacity or explicitly requests it for a
transient operation. Do not retry permanent validation/authentication errors,
unknown broad exceptions, local programmer defects, or irreversible operations
without an idempotency mechanism. Do not add Tenacity around a client that
already owns an adequate retry policy until duplicate retry multiplication is
resolved.
Know the policy objects
| Object |
Meaning |
Required decision |
retry(...) |
Decorator creating a retry controller per call |
Predicate, stop, wait, propagation, hooks. |
Retrying |
Synchronous controller/call/block iterator |
Use when policy is dynamic or a block, not a whole function, is retried. |
AsyncRetrying |
Await-aware controller |
Awaited operation and sleep remain nonblocking. |
| Retry predicate |
Decides whether the last exception/result is retryable |
Must be narrow and domain-grounded. |
| Stop strategy |
Bounds attempts or elapsed retry time |
Always finite in application code. |
| Wait strategy |
Delay before another attempt |
Respect service pressure and add jitter for contention. |
RetryCallState |
Attempt number, outcome, timing, next action |
Source for callbacks and tests, not mutable business state. |
Read the retry state model before composing
predicates, retrying results, or using block iteration.
Ordered workflow
- Name the exact attempted operation and its side effect. Establish whether
repetition is safe, conditionally safe through an idempotency key, or unsafe.
- Classify failures from the real client/library: retry only enumerated
transient exceptions or results. Exclude cancellation, invalid input,
permission/auth failures, and deterministic defects.
- Establish the outer deadline/budget. Choose a finite stop condition that
cannot outlive it; attempts include the first call.
- Choose wait behavior from the dependency contract. For shared remote
services, use capped exponential random wait or honor a supported server
retry delay. Do not busy-loop.
- Decide terminal failure: normally
reraise=True so callers see the final
domain exception. Use RetryError only when callers intentionally consume
retry-controller state.
- Attach structured
before_sleep telemetry without secrets. Log only an
attempt that will actually retry, not every call as an error.
- Test with zero wait/fake sleep and deterministic outcomes. Assert attempt
count, retry classification, final exception/result, and side effects.
Policy decision table
| Condition |
Action |
| Operation is not repeat-safe |
Do not retry; add idempotency/transaction semantics first. |
| Failure is permanent or unknown |
Propagate immediately. |
| Client already retries |
Configure one owner; prevent multiplicative nested attempts. |
| Attempts are cheap and local |
Small attempt bound; wait_none/fixed wait only if pressure is irrelevant. |
| Remote service is contended |
Bounded exponential random wait with cap. |
| Caller has a hard deadline |
Stop by elapsed time/attempts beneath that deadline; retain transport timeout per attempt. |
| Async operation |
Use async-decorated function or AsyncRetrying; never block the loop with time.sleep. |
| Return value signals transient incompleteness |
Use retry_if_result only when the result has an unambiguous retry state. |
Canonical bounded policy
from tenacity import (
retry,
retry_if_exception_type,
stop_after_attempt,
wait_random_exponential,
)
class TemporaryStoreError(Exception):
pass
@retry(
retry=retry_if_exception_type(TemporaryStoreError),
stop=stop_after_attempt(4),
wait=wait_random_exponential(multiplier=0.25, max=4),
reraise=True,
)
def load_record(store, record_id: str):
return store.load(record_id)
The operation still needs a per-attempt timeout. Tenacity bounds retrying, not
an individual call stuck forever. Do not decorate an entire workflow when only
one read is retryable; repeat the narrowest safe unit.
Side effects and nested budgets
- Reads are not automatically safe if they consume messages or advance cursors.
- Writes require a stable idempotency key, transactional upsert, or explicit
proof that a repeated request cannot duplicate effects.
- A timeout does not prove the server did nothing. Treat an ambiguous write
outcome separately from a known pre-commit failure.
- Multiply configured layers to find the real worst-case attempts. Prefer one
retry owner and one outer deadline.
- Do not retry
BaseException, cancellation, keyboard interrupts, or a blanket
Exception just because examples do.
Read policy and testing for budgets, deterministic
tests, and non-idempotent operations.
Async and observability
Tenacity supports coroutines and AsyncRetrying. Ensure the awaited client
call, sleep function, cancellation behavior, and caller deadline all remain
async. In before_sleep, record operation name, attempt, elapsed time, next
delay, and exception class; exclude credentials, payloads, and full response
bodies. A counter should distinguish attempts from completed calls and terminal
failures. Read async and observability.
Version grounding and completion
Inspect the installed version of Tenacity and its signatures before
using a copied helper or callback field. This foundry did not have Tenacity
installed during authoring, so examples are source-grounded but not locally
executed. Do not turn that into a runtime-version claim.
Completion requires: repetition safety is proven; retry and non-retry failures
are enumerated; per-attempt timeout and overall stop are finite; wait avoids a
hot loop; terminal exceptions match the caller contract; async code does not
block; telemetry reveals attempts without leaking data; and deterministic tests
prove attempt counts, terminal behavior, and exactly-once visible effects.
References
- Retry state model
- Policy and deterministic testing
- Async and observability
1---2name: tenacity-python3description: Use for writing, reviewing, debugging, or testing bounded retry policies in Python with Tenacity, including retry predicates, stop and wait strategies, jitter, exception propagation, callbacks, Retrying, and AsyncRetrying. Do not use for generic loops, scheduled jobs, domain polling without a retryable operation, or operations whose side effects are not safe to repeat.4---56# Tenacity Python78Treat a retry as a policy around repeated attempts, not as an error-handling9decorator added after a failure. Preserve the operation's idempotency, deadline,10failure taxonomy, and observability.1112## Boundary1314Use this skill when a project uses Tenacity or explicitly requests it for a15transient operation. Do not retry permanent validation/authentication errors,16unknown broad exceptions, local programmer defects, or irreversible operations17without an idempotency mechanism. Do not add Tenacity around a client that18already owns an adequate retry policy until duplicate retry multiplication is19resolved.2021## Know the policy objects2223| Object | Meaning | Required decision |24|---|---|---|25| `retry(...)` | Decorator creating a retry controller per call | Predicate, stop, wait, propagation, hooks. |26| `Retrying` | Synchronous controller/call/block iterator | Use when policy is dynamic or a block, not a whole function, is retried. |27| `AsyncRetrying` | Await-aware controller | Awaited operation and sleep remain nonblocking. |28| Retry predicate | Decides whether the last exception/result is retryable | Must be narrow and domain-grounded. |29| Stop strategy | Bounds attempts or elapsed retry time | Always finite in application code. |30| Wait strategy | Delay before another attempt | Respect service pressure and add jitter for contention. |31| `RetryCallState` | Attempt number, outcome, timing, next action | Source for callbacks and tests, not mutable business state. |3233Read [the retry state model](references/retry-model.md) before composing34predicates, retrying results, or using block iteration.3536## Ordered workflow37381. Name the exact attempted operation and its side effect. Establish whether39 repetition is safe, conditionally safe through an idempotency key, or unsafe.402. Classify failures from the real client/library: retry only enumerated41 transient exceptions or results. Exclude cancellation, invalid input,42 permission/auth failures, and deterministic defects.433. Establish the outer deadline/budget. Choose a finite stop condition that44 cannot outlive it; attempts include the first call.454. Choose wait behavior from the dependency contract. For shared remote46 services, use capped exponential random wait or honor a supported server47 retry delay. Do not busy-loop.485. Decide terminal failure: normally `reraise=True` so callers see the final49 domain exception. Use `RetryError` only when callers intentionally consume50 retry-controller state.516. Attach structured `before_sleep` telemetry without secrets. Log only an52 attempt that will actually retry, not every call as an error.537. Test with zero wait/fake sleep and deterministic outcomes. Assert attempt54 count, retry classification, final exception/result, and side effects.5556## Policy decision table5758| Condition | Action |59|---|---|60| Operation is not repeat-safe | Do not retry; add idempotency/transaction semantics first. |61| Failure is permanent or unknown | Propagate immediately. |62| Client already retries | Configure one owner; prevent multiplicative nested attempts. |63| Attempts are cheap and local | Small attempt bound; `wait_none`/fixed wait only if pressure is irrelevant. |64| Remote service is contended | Bounded exponential random wait with cap. |65| Caller has a hard deadline | Stop by elapsed time/attempts beneath that deadline; retain transport timeout per attempt. |66| Async operation | Use async-decorated function or `AsyncRetrying`; never block the loop with `time.sleep`. |67| Return value signals transient incompleteness | Use `retry_if_result` only when the result has an unambiguous retry state. |6869## Canonical bounded policy7071```python72from tenacity import (73 retry,74 retry_if_exception_type,75 stop_after_attempt,76 wait_random_exponential,77)787980class TemporaryStoreError(Exception):81 pass828384@retry(85 retry=retry_if_exception_type(TemporaryStoreError),86 stop=stop_after_attempt(4),87 wait=wait_random_exponential(multiplier=0.25, max=4),88 reraise=True,89)90def load_record(store, record_id: str):91 return store.load(record_id)92```9394The operation still needs a per-attempt timeout. Tenacity bounds retrying, not95an individual call stuck forever. Do not decorate an entire workflow when only96one read is retryable; repeat the narrowest safe unit.9798## Side effects and nested budgets99100- Reads are not automatically safe if they consume messages or advance cursors.101- Writes require a stable idempotency key, transactional upsert, or explicit102 proof that a repeated request cannot duplicate effects.103- A timeout does not prove the server did nothing. Treat an ambiguous write104 outcome separately from a known pre-commit failure.105- Multiply configured layers to find the real worst-case attempts. Prefer one106 retry owner and one outer deadline.107- Do not retry `BaseException`, cancellation, keyboard interrupts, or a blanket108 `Exception` just because examples do.109110Read [policy and testing](references/policy-testing.md) for budgets, deterministic111tests, and non-idempotent operations.112113## Async and observability114115Tenacity supports coroutines and `AsyncRetrying`. Ensure the awaited client116call, sleep function, cancellation behavior, and caller deadline all remain117async. In `before_sleep`, record operation name, attempt, elapsed time, next118delay, and exception class; exclude credentials, payloads, and full response119bodies. A counter should distinguish attempts from completed calls and terminal120failures. Read [async and observability](references/async-observability.md).121122## Version grounding and completion123124Inspect the installed version of Tenacity and its signatures before125using a copied helper or callback field. This foundry did not have Tenacity126installed during authoring, so examples are source-grounded but not locally127executed. Do not turn that into a runtime-version claim.128129Completion requires: repetition safety is proven; retry and non-retry failures130are enumerated; per-attempt timeout and overall stop are finite; wait avoids a131hot loop; terminal exceptions match the caller contract; async code does not132block; telemetry reveals attempts without leaking data; and deterministic tests133prove attempt counts, terminal behavior, and exactly-once visible effects.134135## References136137- [Retry state model](references/retry-model.md)138- [Policy and deterministic testing](references/policy-testing.md)139- [Async and observability](references/async-observability.md)