tenacity

Retrying library for Python — provides @retry decorator and Retrying context manager with configurable retry strategies, wait policies, stop conditions, and async support. tenacity features: @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=60), retry=retry_if_exception_type(RequestException)), stop strategies (stop_after_attempt, stop_after_delay, stop_never), wait strategies (wait_fixed, wait_exponential, wait_random, wait_chain, wait_none), retry conditions (retry_if_exception_type, retry_if_result, retry_if_not_result), before/after/before_sleep callbacks, reraise=True option, AsyncRetrying for async, and statistics tracking.

Evaluated Mar 06, 2026 (0d ago) v8.x
Homepage ↗ Repo ↗ Developer Tools python tenacity retry backoff resilience decorators async
⚙ Agent Friendliness
69
/ 100
Can an agent use this?
🔒 Security
90
/ 100
Is it safe for agents?
⚡ Reliability
88
/ 100
Does it work consistently?

Score Breakdown

⚙ Agent Friendliness

MCP Quality
--
Documentation
88
Error Messages
85
Auth Simplicity
99
Rate Limits
99

🔒 Security

TLS Enforcement
90
Auth Strength
90
Scope Granularity
90
Dep. Hygiene
92
Secret Handling
90

Retry utility library. Retrying POST/mutating operations may cause duplicate effects — ensure idempotency before using retry. Exponential backoff without max can cause indefinite delays — always set max. Retry amplifies load on failing services — implement circuit breaker for sustained failures.

⚡ Reliability

Uptime/SLA
88
Version Stability
88
Breaking Changes
85
Error Recovery
90
AF Security Reliability

Best When

Retrying transient failures in API calls, database operations, and network requests — tenacity is the most feature-complete retry library with excellent async support and composable strategies.

Avoid When

Circuit breaker patterns (use pybreaker), rate limiting compliance (use ratelimit), or when backoff library's simpler API suffices.

Use Cases

  • Agent API retry — from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type; @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=30), retry=retry_if_exception_type(requests.RequestException)); def fetch_api(url): return requests.get(url).json() — exponential backoff; agent retries transient failures with backoff
  • Agent retry with logging — @retry(stop=stop_after_attempt(5), wait=wait_exponential(min=1, max=10), before_sleep=before_sleep_log(logger, logging.WARNING)); def call_service(): return service.call() — callback; agent logs each retry attempt; before_sleep= called before sleeping; after= called after each attempt; statistics tracked in function.retry.statistics
  • Agent async retry — from tenacity import AsyncRetrying, stop_after_attempt, wait_fixed; async with AsyncRetrying(stop=stop_after_attempt(3), wait=wait_fixed(2)) as r: async for attempt in r: with attempt: result = await async_operation() — async context; agent retries async operations; AsyncRetrying context manager for fine-grained control
  • Agent retry on result — @retry(retry=retry_if_result(lambda r: r.get('status') == 'pending'), stop=stop_after_attempt(10), wait=wait_fixed(5)); def poll_job(job_id): return api.get_job_status(job_id) — result-based retry; agent polls until operation completes or max retries exceeded; retry_if_result checks return value
  • Agent conditional retry — @retry(stop=stop_after_attempt(3), retry=retry_if_exception_type((ConnectionError, TimeoutError)), reraise=True); def connect(): return create_connection() — selective retry; agent retries only specific exception types; reraise=True re-raises last exception after all retries exhausted (instead of tenacity.RetryError)

Not For

  • Circuit breaker pattern — tenacity is retry-only; for circuit breaker (stop retrying after sustained failures) use pybreaker or custom logic
  • Rate limiting — tenacity retries on failure; for rate limit compliance use ratelimit or per-request delay
  • Async retry with complex flow — for complex async retry with cancel support use anyio or custom async retry logic

Interface

REST API
No
GraphQL
No
gRPC
No
MCP Server
No
SDK
Yes
Webhooks
No

Authentication

Methods: none
OAuth: No Scopes: No

No auth — retry utility library.

Pricing

Model: open_source
Free tier: Yes
Requires CC: No

tenacity is Apache 2.0 licensed. Free for all use.

Agent Metadata

Pagination
none
Idempotent
Partial
Retry Guidance
Not documented

Known Gotchas

  • reraise=False by default — @retry without reraise=True raises tenacity.RetryError after all attempts; original exception wrapped inside RetryError.last_attempt.exception(); agent code: use reraise=True to get original exception type in except clause; or catch tenacity.RetryError and inspect .last_attempt
  • wait_exponential defaults can be surprising — wait_exponential() default: multiplier=1, min=0, max=inf; first retry: 0-1s; grows unbounded; agent code: always specify max: wait_exponential(multiplier=1, min=1, max=60); add wait_random_min/max for jitter: wait_exponential() + wait_random(min=0, max=2)
  • @retry on class methods requires self — @retry cannot be applied as class method decorator directly when it accesses instance state; tenacity.retry wraps function not method; agent code: @retry works fine on class methods for pure retry logic; for instance-level retry configuration: use tenacity.Retrying context manager inside method body
  • stop_after_delay measures wall clock time — stop_after_delay(30) stops after 30 seconds total including all wait times; if wait=wait_fixed(10) and stop_after_delay(30): maximum 3 attempts; agent code: combine stop strategies: stop=(stop_after_attempt(5) | stop_after_delay(60)); | operator means 'stop if either condition met'
  • Async functions need async retry — @retry on regular function works; @retry on async function with tenacity 8.x does work (tenacity detects async); AsyncRetrying context manager for manual control: async with AsyncRetrying(...) as r: async for attempt in r: with attempt: result = await coro(); for explicit async control
  • Statistics tracking requires accessing after call — fn.retry.statistics returns dict only after function called; agent code: call fn(); then log fn.retry.statistics['attempt_number']; or use after= callback: after=after_log(logger, logging.DEBUG); statistics reset on each top-level call; shared across concurrent calls

Alternatives

Full Evaluation Report

Comprehensive deep-dive: security analysis, reliability audit, agent experience review, cost modeling, competitive positioning, and improvement roadmap for tenacity.

AI-powered analysis · PDF + markdown · Delivered within 30 minutes

$99

Package Brief

Quick verdict, integration guide, cost projections, gotchas with workarounds, and alternatives comparison.

Delivered within 10 minutes

$3

Score Monitoring

Get alerted when this package's AF, security, or reliability scores change significantly. Stay ahead of regressions.

Continuous monitoring

$3/mo

Scores are editorial opinions as of 2026-03-06.

5229
Packages Evaluated
26151
Need Evaluation
173
Need Re-evaluation
Community Powered