Skip to main content

pyresilience

CI Coverage PyPI version Python versions Downloads License Documentation

All resilience patterns. One decorator. Zero dependencies.

Inspired by Java's Resilience4j. Stop juggling tenacity for retries, pybreaker for circuit breakers, and custom code for everything else. pyresilience gives you retry, circuit breaker, timeout, fallback, bulkhead, rate limiter, and cache — all through a single @resilient() decorator that works with both sync and async functions.


Install

pip install pyresilience

Also works with uv, poetry, and pdm.

Quick Start

import requests
from pyresilience import resilient, RetryConfig, TimeoutConfig, CircuitBreakerConfig

@resilient(
    retry=RetryConfig(max_attempts=3, delay=1.0),
    timeout=TimeoutConfig(seconds=10),
    circuit_breaker=CircuitBreakerConfig(failure_threshold=5),
)
def call_api(endpoint: str) -> dict:
    return requests.get(endpoint).json()

Retries with exponential backoff. Times out at 10s. Opens the circuit after 5 failures. That's it.

Why pyresilience?

  • One library instead of many — No need to wire together tenacity + pybreaker + custom timeout/fallback/rate limiting code. One config, one decorator.
  • Patterns that work together — Circuit breaker state is shared across retries. Rate limiting respects bulkhead limits. Cache short-circuits the entire pipeline. Everything is coordinated.
  • Zero dependencies — Pure Python stdlib. Nothing to conflict with your stack.
  • Sync and async — Same API for both. Auto-detects your function type.
  • Production observability — Built-in event listeners for logging, metrics, and alerting. OpenTelemetry and Prometheus listeners included. Know when circuits open, retries fire, or rate limits hit.
  • Thread-safe and async-safe — All stateful components use locks. Async-safe latency tracking via contextvars. Cache stampede prevention via per-key locking.
  • Framework integrations — Drop-in support for FastAPI, Django, and Flask.

All Seven Patterns

from pyresilience import resilient, RetryConfig, TimeoutConfig, CircuitBreakerConfig
from pyresilience import FallbackConfig, BulkheadConfig, RateLimiterConfig, CacheConfig

@resilient(
    retry=RetryConfig(max_attempts=3, delay=1.0, backoff_factor=2.0),
    timeout=TimeoutConfig(seconds=10),
    circuit_breaker=CircuitBreakerConfig(failure_threshold=5, recovery_timeout=30),
    fallback=FallbackConfig(handler=lambda e: {"status": "degraded"}, fallback_on=[Exception]),
    bulkhead=BulkheadConfig(max_concurrent=10),
    rate_limiter=RateLimiterConfig(max_calls=100, period=60.0),
    cache=CacheConfig(ttl=300.0, max_size=1000),
)
def call_service(endpoint: str) -> dict:
    return requests.get(endpoint).json()
Pattern Config What it does
Retry RetryConfig Exponential backoff with jitter
Timeout TimeoutConfig Per-call time limits
Circuit Breaker CircuitBreakerConfig Stop calling failing services
Fallback FallbackConfig Graceful degradation
Bulkhead BulkheadConfig Concurrency limiting
Rate Limiter RateLimiterConfig Token bucket rate limiting
Cache CacheConfig LRU result caching with TTL

Async Support

The same decorator works with async functions — no changes needed:

import aiohttp
from pyresilience import resilient, RetryConfig, CircuitBreakerConfig

@resilient(
    retry=RetryConfig(max_attempts=3, delay=0.5),
    circuit_breaker=CircuitBreakerConfig(failure_threshold=5),
)
async def call_api(url: str) -> dict:
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.json()

Built-in Presets

Skip the configuration for common use cases:

from pyresilience import resilient
from pyresilience import http_policy, db_policy, queue_policy, strict_policy, llm_policy

@resilient(**http_policy())       # 10s timeout, 3 retries, circuit breaker
def call_api(): ...

@resilient(**db_policy())         # 30s timeout, 2 retries, 10 concurrent max
def query_db(): ...

@resilient(**queue_policy())      # 15s timeout, 5 retries, high failure threshold
async def publish_message(): ...

@resilient(**strict_policy())     # 5s timeout, 1 retry, fail fast
def latency_critical(): ...

@resilient(**llm_policy())        # 429-aware retry honoring Retry-After + client-side rate limit
def ask_model(): ...

HTTP 429 / Retry-After out of the box

Calling OpenAI, Anthropic, Stripe, or GitHub APIs? pyresilience handles rate limits natively — no hand-rolled wait strategies:

from pyresilience import resilient, RetryConfig
from pyresilience.contrib.http import retry_on_status, retry_after_delay

@resilient(retry=RetryConfig(
    max_attempts=4,
    retry_on_result=retry_on_status(429, 503),    # retry these statuses (requests/httpx/aiohttp)
    delay_func=retry_after_delay(max_wait=60.0),  # wait exactly what Retry-After asks
    ignore_on=(PermissionError,),                 # terminal errors: never retried
))
def call_api():
    return requests.get("https://api.example.com/data")

Observability

from pyresilience import resilient, RetryConfig, JsonEventLogger, MetricsCollector

logger = JsonEventLogger()
metrics = MetricsCollector()

@resilient(retry=RetryConfig(max_attempts=3), listeners=[logger, metrics])
def my_func():
    ...

# After calls:
print(metrics.summary())
# {"my_func": {"events": {"retry": 2, "success": 1}, "success_rate": 1.0, "avg_latency_ms": 15.2}}

Request Correlation

from pyresilience import resilience_context

# Set trace/request ID for the current context — propagates through all resilience events
resilience_context.set({"trace_id": "abc-123", "request_id": "req-456"})

OpenTelemetry & Prometheus

from pyresilience.contrib.otel import OpenTelemetryListener
from pyresilience.contrib.prometheus import PrometheusListener

@resilient(retry=RetryConfig(max_attempts=3), listeners=[OpenTelemetryListener()])
def call_api(): ...

@resilient(retry=RetryConfig(max_attempts=3), listeners=[PrometheusListener()])
def call_db(): ...

Production Features

Retry Budget

Prevent retry storms across your service with a shared token bucket:

from pyresilience import resilient, RetryConfig, RetryBudgetConfig, RetryBudget

budget = RetryBudget(RetryBudgetConfig(max_retries=100, refill_rate=10))

@resilient(retry=RetryConfig(max_attempts=3, retry_budget=budget))
def call_api(): ...

Per-Attempt Timeout

Apply timeout per attempt instead of a total deadline:

from pyresilience import resilient, TimeoutConfig

@resilient(timeout=TimeoutConfig(seconds=5, per_attempt=True))   # 5s per attempt
def call_api(): ...

@resilient(timeout=TimeoutConfig(seconds=30, per_attempt=False))  # 30s total deadline
def call_db(): ...

Health Check

Inspect circuit breaker states across your registry:

from pyresilience import ResilienceRegistry, health_check

registry = ResilienceRegistry()
# ... register and use services ...

status = health_check(registry)
# {"payment-api": "CLOSED", "inventory-api": "OPEN"}

Graceful Shutdown

Drain in-flight calls before stopping:

from pyresilience import shutdown

shutdown(wait=True, timeout=30)  # Wait up to 30s for in-flight calls to complete

Performance

Benchmarked against tenacity, backoff, stamina, and pybreaker on macOS (Apple Silicon). Full benchmark code in benchmarks/.

Decorator Overhead (no-op function, 100k calls)

Library Mean vs pyresilience
bare (no decorator) 0.07μs —
pyresilience 0.64μs 1.0x
pybreaker 0.64μs 1.0x
backoff 1.29μs 2.0x slower
stamina 5.33μs 8.3x slower
tenacity 6.64μs 10.4x slower

pyresilience is 10.4x faster than tenacity on the happy path.

Individual Pattern Overhead (100k calls)

Pattern Mean Latency
Retry (happy path) 0.64μs
Circuit Breaker 1.03μs
Fallback (triggered) 0.69μs
Bulkhead 0.74μs
Rate Limiter 0.89μs
Cache (hit) 0.68μs
All 7 patterns (cache hit) 0.67μs

Throughput (10k calls, 10 threads)

Library ops/sec
pyresilience 223,934
tenacity 58,109

pyresilience achieves 3.9x higher throughput under concurrent load.

Async Overhead (50k calls)

Library Mean
pyresilience 0.82μs
tenacity 11.83μs

pyresilience is 14.4x faster than tenacity for async functions.

Memory (1,000 decorated functions)

Library Memory
pyresilience 1,224 KB
tenacity 2,150 KB

pyresilience uses 43% less memory.

Comparison

pyresilience tenacity pybreaker backoff stamina
Retry Yes Yes - Yes Yes
Circuit Breaker Yes - Yes - -
Timeout Yes - - - -
Fallback Yes - - - -
Bulkhead Yes - - - -
Rate Limiter Yes - - - -
Cache Yes - - - -
Retry Budget Yes - - - -
429 + Retry-After handling Yes - - - -
Context Propagation Yes - - - -
Health Check Yes - - - -
Prometheus Yes - - - -
OpenTelemetry Yes - - - -
Unified API Yes - - - -
Zero Dependencies Yes Yes - - -
Async Yes Yes - Yes Yes

Comparison reflects built-in capabilities and unified API model, not every possible custom composition.

Documentation

Full guides, API reference, and examples at pyresilience.readthedocs.io.

License

MIT

Release files for pyresilience 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyresilience 0.4.0
File Size Uploaded
pyresilience-0.4.0.tar.gz 37.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyresilience 0.4.0
File Interpreter ABI Platform
pyresilience-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 82.5 kB

Release files / pyresilience-0.4.0.tar.gz

Download URL pyresilience-0.4.0.tar.gz
Size 37.4 kB
Tags Source
SHA-256 checksum
How to use checksums
4016917efe4a85fd8f664c60a25e2707bf0188b4d4e90602a1fac6297071119d
BLAKE2b-256 checksum
How to use checksums
bc5f245d51ff9a7a3e22288c3fbbac705eaac76b6b89360e466f33ec32b7a024
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release files / pyresilience-0.4.0-py3-none-any.whl

Download URL pyresilience-0.4.0-py3-none-any.whl
Size 45.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
82808eb0956833aeeb09fb616818450a1f69a0891ef95c60ca0507e52bc5bb53
BLAKE2b-256 checksum
How to use checksums
449495e8268f8b17975c1004a48330b472a0414e20295e1f426aa4a1987bcc84
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page