Unified resilience patterns for Python — retry, circuit breaker, timeout, fallback, bulkhead, rate limiter, and cache in one decorator.
Project description
pyresilience
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
@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(): ...
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 | - | - | - | - |
| 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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyresilience-0.3.2.tar.gz.
File metadata
- Download URL: pyresilience-0.3.2.tar.gz
- Upload date:
- Size: 32.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
323f65e39950a26b9a17de2f0538de819ea9aca7612da0bda414275f59b8c9c2
|
|
| MD5 |
4f3d2d24cd9f5c5caab01894b5e935d1
|
|
| BLAKE2b-256 |
1553968f5b12a39ecd5dcfd30664be28740d03e05d74b73957589f23daf8b213
|
Provenance
The following attestation bundles were made for pyresilience-0.3.2.tar.gz:
Publisher:
ci.yml on AhsanSheraz/pyresilience
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyresilience-0.3.2.tar.gz -
Subject digest:
323f65e39950a26b9a17de2f0538de819ea9aca7612da0bda414275f59b8c9c2 - Sigstore transparency entry: 1153367986
- Sigstore integration time:
-
Permalink:
AhsanSheraz/pyresilience@20cbb1094f4f93f2be6bb9d75ae6999a51a6d15f -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/AhsanSheraz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@20cbb1094f4f93f2be6bb9d75ae6999a51a6d15f -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyresilience-0.3.2-py3-none-any.whl.
File metadata
- Download URL: pyresilience-0.3.2-py3-none-any.whl
- Upload date:
- Size: 41.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71653e9df760c1336620b6b9c289316e26bc05f95ac89c38043e8e61188ec5af
|
|
| MD5 |
d9c15f622f71d91ad28539c4abefb3f1
|
|
| BLAKE2b-256 |
4dc0f2bb97d3db479cf33d045b6f8f90bfa398051e96fb80fbbc1647af71e748
|
Provenance
The following attestation bundles were made for pyresilience-0.3.2-py3-none-any.whl:
Publisher:
ci.yml on AhsanSheraz/pyresilience
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyresilience-0.3.2-py3-none-any.whl -
Subject digest:
71653e9df760c1336620b6b9c289316e26bc05f95ac89c38043e8e61188ec5af - Sigstore transparency entry: 1153367987
- Sigstore integration time:
-
Permalink:
AhsanSheraz/pyresilience@20cbb1094f4f93f2be6bb9d75ae6999a51a6d15f -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/AhsanSheraz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@20cbb1094f4f93f2be6bb9d75ae6999a51a6d15f -
Trigger Event:
push
-
Statement type: