Skip to main content

Function decoration for backoff and retry

Project description

backon

Function decoration for backoff and retry — modern, fast, zero dependencies.

CI Coverage CodeQL PyPI Python License PyPI Downloads

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 def functions
  • Full type hints — validated with mypy, strict mode compatible
  • Global togglebackon.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 callbackson_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

MIT

Made by Llucs with ❤️

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

backon-3.6.1.tar.gz (40.0 kB view details)

Uploaded Source

Built Distribution

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

backon-3.6.1-py3-none-any.whl (28.2 kB view details)

Uploaded Python 3

File details

Details for the file backon-3.6.1.tar.gz.

File metadata

  • Download URL: backon-3.6.1.tar.gz
  • Upload date:
  • Size: 40.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for backon-3.6.1.tar.gz
Algorithm Hash digest
SHA256 ce801268a4afe5073ca31a1443adc433eef3d72b73d39b9465aa2c1b521d622c
MD5 223844ba5aeea65447636446c6e8d6b9
BLAKE2b-256 5284a602be18b1bc3fe52e88abd38aef6a881e0252b915848b24e00847331e04

See more details on using hashes here.

Provenance

The following attestation bundles were made for backon-3.6.1.tar.gz:

Publisher: release.yml on Llucs/backon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file backon-3.6.1-py3-none-any.whl.

File metadata

  • Download URL: backon-3.6.1-py3-none-any.whl
  • Upload date:
  • Size: 28.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for backon-3.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 89b796ed9087c669ceb0abf684b53eb419ab2222085e67f2054ee8bee8370aa9
MD5 1683682f3ff8648da186cd0b3e3ba615
BLAKE2b-256 fcc32c818fd16bc4d0b2fda74410ff0d7cd46e65f2e37e18028c122208ddc3b3

See more details on using hashes here.

Provenance

The following attestation bundles were made for backon-3.6.1-py3-none-any.whl:

Publisher: release.yml on Llucs/backon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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