API resilience library — exponential backoff and circuit breaker
Project description
smoothapi-py
API resilience library for Python. It provides a decorator to wrap your HTTP requests with exponential backoff, full jitter, and a finite-state machine circuit breaker to protect against cascading failures.
Zero dependencies. Fully typed. Supports both sync and async functions out of the box. Automatically integrates with requests and httpx if installed.
Install
pip install smoothapi-py
Features
- Exponential Backoff with Full Jitter: Prevents the "thundering herd" problem by randomizing retry delays.
- Circuit Breaker (FSM): Isolated state machine (
CLOSED→OPEN→HALF_OPEN) per decorated function. Thread-safe execution. - Smart Retries: Automatically detects HTTP status codes from
requestsandhttpxexceptions. Retries on transient codes (429, 500, 502, 503, 504) and re-raises client errors immediately. - Graceful Fallbacks: Optionally return cached or default data instantly when the circuit is
OPEN, bypassing network IO entirely.
Usage
Basic Usage (Defaults)
If you don't need custom configurations, you can instantiate the decorator with its defaults by passing an empty config object.
import requests
from smooth_api import resilient_api, ResilientConfig
# Create it with default settings
config = ResilientConfig()
@resilient_api(config)
def get_user_data(user_id: str):
res = requests.get(f"https://api.example.com/users/{user_id}")
res.raise_for_status() # Always raise so the decorator knows it failed!
return res.json()
# Standard usage
try:
data = get_user_data("123")
print(data)
except Exception as e:
print("Request failed completely:", e)
Default Settings provided automatically:
- Retries: 3 attempts
- Backoff Base Delay: 0.1 seconds (100 milliseconds)
- Circuit Failure Threshold: Trips after 3 consecutive failures
- Circuit Cooldown: Stays open for 10 seconds before probing
- Status Codes to Retry:
429,500,502,503, and504
Advanced Usage (Custom Settings)
You can override any of the defaults to suit your application's needs, such as adding a fallback object.
from smooth_api import resilient_api, ResilientConfig
from smooth_api.config import BackoffConfig, CircuitBreakerConfig
import requests
config = ResilientConfig(
backoff=BackoffConfig(
base_delay=0.1, # seconds to wait before first retry
max_delay=30.0, # cap on exponential growth
max_retries=3 # max number of retry attempts
),
circuit_breaker=CircuitBreakerConfig(
failure_threshold=3, # trip OPEN after 3 consecutive failures
cooldown_ms=10_000 # stay OPEN for 10 seconds before probing
),
# Optional: return this exact object when the circuit is OPEN
fallback={"status": "degraded", "data": []},
# Optional: HTTP status codes to trigger a retry
retry_on=[429, 500, 502, 503, 504]
)
@resilient_api(config)
def get_user_data(user_id: str):
res = requests.get(f"https://api.example.com/users/{user_id}")
res.raise_for_status()
return res.json()
# You can also override the fallback at runtime per-call:
data = get_user_data("456", fallback={"status": "override"})
Client Error Handling & Warnings
By default, non-retryable client errors (e.g. 400, 401, 403, 404, 405) bubble up and raise exceptions immediately. If you want to intercept these client errors:
from smooth_api import resilient_api, ResilientConfig
def my_callback(status: int, message: str):
print(f"Error hook: Received client error {status}")
config = ResilientConfig(
fallback_on_non_retryable=True,
# Optional: Custom error hook function
on_non_retryable_error=my_callback,
# Optional: Fallback returned on non-retryable errors
fallback={"status": "error", "message": "Not Found"}
)
- Default Warning: If
fallback_on_non_retryableisTrueand no customon_non_retryable_erroris defined, it will write a warning message tosys.stderr. - Graceful Return: If no
fallbackis configured, it returns a mockResponsewrapper withstatus_code,.json()returning{"error": True, "status": status, "message": "..."}, and.okreturningFalse. Code downstream can checkres.status_codeor callres.json()without raising exceptions.
### Async Support
The decorator automatically detects if your function is a coroutine and uses `asyncio.sleep` instead of blocking the thread:
```python
import httpx
@resilient_api(config)
async def get_user_data_async(user_id: str):
async with httpx.AsyncClient() as client:
res = await client.get(f"https://api.example.com/users/{user_id}")
res.raise_for_status()
return res.json()
How It Works
- Isolation: The circuit breaker state is isolated per decorated function (
fn.__qualname__). - Circuit Check: Before execution, the breaker checks the state. If it's
OPEN, the request is blocked instantly (returning your fallback, or raisingRuntimeError). - Execution & Retries: If an exception is raised, it attempts to extract the HTTP status code (supports
requestsandhttpx). If the status is inretry_on, it's counted as a failure and the thread sleeps with backoff. - Recovery: After
cooldown_ms, the breaker entersHALF_OPEN. The next execution acts as a probe. If it succeeds, the circuit closes. If it fails, it snaps back toOPENimmediately.
License
MIT
Project details
Release history Release notifications | RSS feed
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 smoothapi_py-1.0.0.tar.gz.
File metadata
- Download URL: smoothapi_py-1.0.0.tar.gz
- Upload date:
- Size: 8.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
354af230e865ef4b7853af0f791df3afe281af8a2c33f3a2c720ad4ed8b796dc
|
|
| MD5 |
7f9df3998d18d9221fd1a066c24f6b1c
|
|
| BLAKE2b-256 |
b5594d340f7c440bbd8b45c5b965f79fb853da347190bd7db7173070ba5d97f9
|
File details
Details for the file smoothapi_py-1.0.0-py3-none-any.whl.
File metadata
- Download URL: smoothapi_py-1.0.0-py3-none-any.whl
- Upload date:
- Size: 7.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f2037f78370dcda3b63bcb5a86aede8b87fb6f9321195bfee230d443ccae304
|
|
| MD5 |
10914c6cff1e41fd4d8961d8c12e725e
|
|
| BLAKE2b-256 |
fa4a99650081adba0381f41a92cbe88591ea4a3800dfe71dd831d77cb3807dbe
|