Skip to main content

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.

PyPI version Python versions CI License: MIT Open Source


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_success and on_failure hooks
  • Retry budget (max total wait time across all retries)
  • More backoff strategies (fibonacci, decorrelated jitter)

License

MIT © Sufiyan Khan

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

backoffkit-0.1.0.tar.gz (6.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

backoffkit-0.1.0-py3-none-any.whl (5.5 kB view details)

Uploaded Python 3

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

Hashes for backoffkit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0367dbec7cf4ee07429abc639147f8d3802fc00c50a1f371abfa31b77f6e9afc
MD5 3a16e46581391389197e5522f60c1b85
BLAKE2b-256 77026510159abecb6821b0b69e8f8f4f9796ccafae63ef9bb09618b009a67cd0

See more details on using hashes here.

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

Hashes for backoffkit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8d412de5ad8084dc14aa7d9a5657af33e8cbf5114ced40dde4dae40e8937f806
MD5 7a27238684fcb56bb7ff59a757a756e7
BLAKE2b-256 68c1beed9ef7b137a822e117430d0407f881ffed304243575a36eb87a3ee32fd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page