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
Metadata
Release files for backoffkit 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| backoffkit-0.1.0.tar.gz | 6.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| backoffkit-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 11.6 kB
Release files / backoffkit-0.1.0.tar.gz
| Download URL | backoffkit-0.1.0.tar.gz |
|---|---|
| Size | 6.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0367dbec7cf4ee07429abc639147f8d3802fc00c50a1f371abfa31b77f6e9afc
|
|
BLAKE2b-256 checksum How to use checksums |
77026510159abecb6821b0b69e8f8f4f9796ccafae63ef9bb09618b009a67cd0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Release files / backoffkit-0.1.0-py3-none-any.whl
| Download URL | backoffkit-0.1.0-py3-none-any.whl |
|---|---|
| Size | 5.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8d412de5ad8084dc14aa7d9a5657af33e8cbf5114ced40dde4dae40e8937f806
|
|
BLAKE2b-256 checksum How to use checksums |
68c1beed9ef7b137a822e117430d0407f881ffed304243575a36eb87a3ee32fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|