Retry decorator with exponential backoff and jitter. Works with sync and async functions. Zero dependencies.
Project description
backoffkit
Retry decorator with exponential backoff and jitter. Works with sync and async functions. Zero dependencies.
Any time your code calls something external — an API, a database, a third-party service — it can fail temporarily. The fix is to retry, but retrying naively (immediately, forever) makes things worse.
backoffkit gives you smart retry logic in one decorator:
from backoffkit import retry
@retry(times=3, backoff="exponential")
def call_api():
response = requests.get("https://api.example.com/data")
response.raise_for_status()
return response.json()
It works the same way for async functions:
@retry(times=3, backoff="exponential")
async def fetch_data():
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
response.raise_for_status()
return response.json()
Install
pip install backoffkit
Usage
Basic retry
from backoffkit import retry
@retry(times=3)
def call_api():
...
Retry only on specific exceptions
@retry(times=3, on=[ConnectionError, TimeoutError])
def call_api():
...
Log retries with a callback
import logging
@retry(
times=5,
on=[ConnectionError],
on_retry=lambda attempt, exc: logging.warning(f"Retry {attempt}: {exc}")
)
def call_api():
...
Async support
@retry(times=3, backoff="exponential")
async def fetch():
...
Backoff strategies
| Strategy | Description | Delays (delay=1s) |
|---|---|---|
exponential |
Doubles each attempt (default) | 1s, 2s, 4s, 8s... |
linear |
Increases by delay each attempt | 1s, 2s, 3s, 4s... |
fixed |
Same delay every attempt | 1s, 1s, 1s, 1s... |
Jitter
Jitter adds a small random noise to each delay to prevent the thundering herd problem — when many clients retry at exactly the same time and overwhelm the server. Enabled by default.
@retry(times=3, backoff="exponential", jitter=True) # default
def call_api():
...
All options
@retry(
times=3, # Total attempts including the first (default: 3)
delay=1.0, # Initial delay in seconds (default: 1.0)
backoff="exponential", # "fixed", "linear", or "exponential" (default: "exponential")
jitter=True, # Add random noise to delay (default: True)
max_delay=60.0, # Cap on delay in seconds (default: 60.0)
on=[Exception], # Exceptions to retry on (default: all)
on_retry=None, # Callback: fn(attempt: int, exc: Exception) -> None
)
Error handling
When all attempts are exhausted, RetryError is raised with the attempt count and the last exception:
from backoffkit import retry, RetryError
@retry(times=3, delay=0)
def fn():
raise ConnectionError("service down")
try:
fn()
except RetryError as e:
print(e.attempts) # 3
print(e.last_exception) # ConnectionError: service down
Production use
backoffkit is designed for production backend systems. Use it when calling:
- External REST APIs
- Databases that may have transient connection failures
- Message queues
- Any I/O-bound operation that can fail temporarily
The exponential backoff + jitter combination is the industry-standard approach used by AWS, Google, and Stripe SDKs.
Why not tenacity?
tenacity is powerful but has a large API surface. backoffkit covers the 95% use case with a single decorator and zero dependencies — no learning curve, no extras to install.
Open Source
backoffkit is MIT licensed and open for contributions. See CONTRIBUTING.md.
Things we'd love help with:
- Async callback support in
on_retry on_successandon_failurehooks- Retry budget (max total wait time across all retries)
- More backoff strategies (fibonacci, decorrelated jitter)
License
MIT © Sufiyan Khan
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 backoffkit-0.1.0.tar.gz.
File metadata
- Download URL: backoffkit-0.1.0.tar.gz
- Upload date:
- Size: 6.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0367dbec7cf4ee07429abc639147f8d3802fc00c50a1f371abfa31b77f6e9afc
|
|
| MD5 |
3a16e46581391389197e5522f60c1b85
|
|
| BLAKE2b-256 |
77026510159abecb6821b0b69e8f8f4f9796ccafae63ef9bb09618b009a67cd0
|
File details
Details for the file backoffkit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: backoffkit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 5.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d412de5ad8084dc14aa7d9a5657af33e8cbf5114ced40dde4dae40e8937f806
|
|
| MD5 |
7a27238684fcb56bb7ff59a757a756e7
|
|
| BLAKE2b-256 |
68c1beed9ef7b137a822e117430d0407f881ffed304243575a36eb87a3ee32fd
|