Function decoration for backoff and retry
Project description
backon
Function decoration for backoff and retry — modern, fast, zero dependencies.
backon is a modern evolution of backoff — a zero-dependency Python library for retry with exponential backoff. It provides decorator, functional, and context manager APIs for both sync and async code.
Features
- Zero dependencies — pure Python, stdlib only
- Three APIs — decorator (
@on_exception,@on_predicate), functional (retry()), context manager (Retrying) - Async native — same API works for
async deffunctions - Full type hints — validated with mypy, strict mode compatible
- Global toggle —
backon.disable()/backon.enable()for testing - Custom sleep — inject your own sleep function (useful for testing with
asyncio.Event) - Multiple wait strategies — exponential, constant, Fibonacci, decay, runtime, and composable chains
- Jitter — full jitter, random jitter, or none
- Rich callbacks —
on_attempt,on_backoff,on_success,on_giveup,before_sleep - Modern packaging — PEP 621, PDM, py.typed
Installation
pip install backon
Requires Python 3.10+.
Quick Start
Retry on exception
import backon
@backon.on_exception(backon.expo, ValueError, max_tries=3)
def fetch_data():
return api.call()
Retry on predicate
@backon.on_predicate(backon.constant, max_tries=5, interval=0.5)
def poll_status():
return check_ready()
Functional API
result = backon.retry(
fetch_data,
backon.expo,
exception=ValueError,
max_tries=3,
)
Context manager
with backon.Retrying(backon.expo, exception=ValueError, max_tries=3) as r:
result = r.call(fetch_data)
Async variant:
async with backon.Retrying(backon.constant, exception=ValueError, max_tries=3, interval=0.5) as r:
result = await r.async_call(fetch_data)
API Reference
Decorators
@backon.on_exception(wait_gen, exception, ...)
Retry when the decorated function raises one of the specified exceptions.
@backon.on_exception(backon.expo, (ValueError, TimeoutError), max_tries=5)
def fetch():
...
Parameters:
| Argument | Type | Default | Description |
|---|---|---|---|
wait_gen |
WaitGenerator |
— | Wait strategy (expo, constant, fibo, etc.) |
exception |
type or tuple[type] |
— | Exception class(es) to retry on |
max_tries |
int |
None |
Maximum number of attempts |
max_time |
float |
None |
Maximum total elapsed time |
jitter |
Jitterer or None |
full_jitter |
Jitter function |
giveup |
Callable[[Exception], bool] |
lambda e: False |
Stop retrying for matching exceptions |
on_success |
Handler |
None |
Called after successful attempt |
on_backoff |
Handler |
None |
Called before each retry |
on_giveup |
Handler |
None |
Called when retries exhausted |
on_attempt |
Handler |
None |
Called before each attempt |
before_sleep |
Handler |
None |
Called before sleeping |
logger |
str or Logger |
"backon" |
Logger name or instance |
raise_on_giveup |
bool |
True |
Raise final exception when giving up |
sleep |
Callable[[float], Any] |
None |
Custom sleep function |
@backon.on_predicate(wait_gen, predicate, ...)
Retry while the predicate matches the return value.
@backon.on_predicate(backon.constant, predicate=lambda x: x is None, max_tries=5)
def poll():
...
Functional API
backon.retry(target, wait_gen, ...)
result = backon.retry(
target=my_function,
wait_gen=backon.expo,
exception=ValueError,
max_tries=3,
jitter=backon.full_jitter,
)
Accepts all the same parameters as the decorators, plus wait_gen_kwargs as extra keyword arguments (e.g. interval=0.5 for constant).
Context Manager
backon.Retrying(wait_gen, ...)
with backon.Retrying(backon.expo, exception=ValueError, max_tries=3) as r:
r.call(my_function)
async with backon.Retrying(backon.constant, exception=ValueError, max_tries=3, interval=0.5) as r:
await r.async_call(my_async_function)
Methods:
| Method | Description |
|---|---|
call(target, *args, **kwargs) |
Execute synchronously |
async_call(target, *args, **kwargs) |
Execute asynchronously |
Wait Generators
| Generator | Signature | Description |
|---|---|---|
expo |
(base=2, factor=1, max_value=None) |
Exponential backoff: factor * base^n |
constant |
(interval=1) |
Fixed interval; accepts float or Sequence[float] |
fibo |
(max_value=None) |
Fibonacci sequence |
runtime |
(value=Callable) |
Dynamic wait from return value or exception |
decay |
(initial_value=1, decay_factor=1, min_value=None) |
Exponential decay |
wait_random_exponential |
(multiplier=1, max_value=None, exp_base=2, min_value=0) |
Randomized exponential |
wait_incrementing |
(start=1, increment=1, max_value=None) |
Linear increment |
Jitter
@backon.on_exception(backon.expo, ValueError, jitter=backon.full_jitter)
def f():
...
| Jitter | Effect |
|---|---|
backon.full_jitter |
Random value between 0 and the wait time |
backon.random_jitter |
Random value within ±25% of the wait time |
None |
No jitter (deterministic waits) |
Handlers
Handlers receive a details dict with contextual information:
def handler(details):
print(f"Attempt {details['tries']}, elapsed {details['elapsed']:.2f}s")
@backon.on_exception(
backon.expo, ValueError, max_tries=3,
on_attempt=handler,
on_backoff=handler,
on_success=handler,
on_giveup=handler,
)
def f():
...
Available keys in details:
| Key | Available in |
|---|---|
target |
All |
args, kwargs |
All |
tries |
All |
elapsed |
All |
value |
on_success, on_backoff, on_giveup |
exception |
on_backoff, on_giveup |
wait |
on_backoff |
Global Toggle
Useful in tests to disable retry logic:
backon.disable() # skip retry, call function directly
backon.enable() # re-enable retry
Async Support
All three APIs work with async functions transparently:
@backon.on_exception(backon.expo, ValueError, max_tries=3)
async def fetch():
return await api.call()
result = await backon.retry(fetch, backon.expo, exception=ValueError, max_tries=3)
async with backon.Retrying(backon.expo, exception=ValueError, max_tries=3) as r:
result = await r.async_call(fetch)
Custom Sleep
Replace the default sleep for testing or special environments:
@backon.on_exception(
backon.expo, ValueError, max_tries=3,
sleep=lambda s: print(f"waiting {s}s"),
)
def f():
...
# With asyncio.Event for testing
import asyncio
event = asyncio.Event()
@backon.on_exception(
backon.expo, ValueError, max_tries=3,
sleep=backon.sleep_using_event(event),
)
async def f():
...
Migrating from backoff
backon is a near-drop-in replacement. Change your imports:
- import backoff
+ import backon
- @backoff.on_exception(backoff.expo, ValueError, max_tries=3)
+ @backon.on_exception(backon.expo, ValueError, max_tries=3)
Key differences:
| Area | backoff | backon |
|---|---|---|
| Python support | 3.7+ | 3.10+ |
| Type hints | Partial | Full |
on_attempt callback |
Not supported | Supported |
| Context manager | Not supported | Retrying class |
| Functional API | Not supported | retry() function |
| Global toggle | Not supported | disable() / enable() |
| Custom sleep | Not supported | sleep= parameter |
| Build system | Poetry | PDM (PEP 621) |
Contributing
git clone https://github.com/Llucs/backon.git
cd backon
pip install pdm
pdm install
pdm run ruff check backon/ tests/
pdm run mypy backon/
pdm run pytest tests/
License
Made by Llucs with ❤️
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 backon-3.5.0.tar.gz.
File metadata
- Download URL: backon-3.5.0.tar.gz
- Upload date:
- Size: 37.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a341196fc059b492665ca2a1bf5bbcdeeb10ae95b184dbefc97b25e1c8ca85e
|
|
| MD5 |
efcc930c3cf33af9a5851b6403ee0620
|
|
| BLAKE2b-256 |
fb4369ad47b44716dbff24a41a98bcfd722288fd9b0d45021ec8c25fe8bbef2e
|
Provenance
The following attestation bundles were made for backon-3.5.0.tar.gz:
Publisher:
release.yml on Llucs/backon
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
backon-3.5.0.tar.gz -
Subject digest:
7a341196fc059b492665ca2a1bf5bbcdeeb10ae95b184dbefc97b25e1c8ca85e - Sigstore transparency entry: 2020845052
- Sigstore integration time:
-
Permalink:
Llucs/backon@373277e2b82c96cbb877e8de0d4db6126a3877da -
Branch / Tag:
refs/tags/v3.5.0 - Owner: https://github.com/Llucs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@373277e2b82c96cbb877e8de0d4db6126a3877da -
Trigger Event:
release
-
Statement type:
File details
Details for the file backon-3.5.0-py3-none-any.whl.
File metadata
- Download URL: backon-3.5.0-py3-none-any.whl
- Upload date:
- Size: 26.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e4a664123611c45ff316645c824af8341d5834ea1fe6a3313ab5a95a156c30b
|
|
| MD5 |
c347b3ecebbce527a5a0dc632dcafaf1
|
|
| BLAKE2b-256 |
cb6ff085068bf58f2ed6c9197dd6575a5fd6618c240fef2f85999c23389f1b51
|
Provenance
The following attestation bundles were made for backon-3.5.0-py3-none-any.whl:
Publisher:
release.yml on Llucs/backon
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
backon-3.5.0-py3-none-any.whl -
Subject digest:
0e4a664123611c45ff316645c824af8341d5834ea1fe6a3313ab5a95a156c30b - Sigstore transparency entry: 2020845341
- Sigstore integration time:
-
Permalink:
Llucs/backon@373277e2b82c96cbb877e8de0d4db6126a3877da -
Branch / Tag:
refs/tags/v3.5.0 - Owner: https://github.com/Llucs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@373277e2b82c96cbb877e8de0d4db6126a3877da -
Trigger Event:
release
-
Statement type: