Decorators
Detailed guide to backoff decorators.
on_exception
The on_exception decorator retries a function when a specified exception is raised.
Basic Usage
import backoff
import requests
@backoff.on_exception(backoff.expo,
requests.exceptions.RequestException)
def get_url(url):
return requests.get(url)
Parameters
- wait_gen - Wait strategy generator (expo, fibo, constant, runtime)
- exception - Exception class or tuple of exception classes to catch
- max_tries - Maximum number of attempts (default: None = unlimited)
- max_time - Maximum total time in seconds (default: None = unlimited)
- jitter - Jitter function to add randomness (default: full_jitter)
- giveup - Function to determine if exception is non-retryable
- on_success - Callback when function succeeds
- on_backoff - Callback when backing off
- on_giveup - Callback when giving up
- raise_on_giveup - Whether to raise exception on giveup (default: True)
- logger - Logger for retry events (default: 'backoff' logger)
Multiple Exceptions
Handle different exceptions with the same backoff:
@backoff.on_exception(
backoff.expo,
(requests.exceptions.Timeout,
requests.exceptions.ConnectionError,
requests.exceptions.HTTPError)
)
def make_request(url):
return requests.get(url)
Conditional Giveup
Customize when to stop retrying:
def is_fatal(e):
"""Don't retry on client errors"""
if hasattr(e, 'response') and e.response is not None:
return 400 <= e.response.status_code < 500
return False
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
giveup=is_fatal,
max_time=300
)
def api_call(endpoint):
response = requests.get(endpoint)
response.raise_for_status()
return response.json()
Suppress Exceptions on Giveup
Return None instead of raising when all retries are exhausted:
@backoff.on_exception(
backoff.expo,
requests.exceptions.RequestException,
max_tries=5,
raise_on_giveup=False
)
def optional_request(url):
return requests.get(url)
# Returns None if all retries fail
result = optional_request("https://example.com")
on_predicate
The on_predicate decorator retries when a condition is true about the return value.
Basic Usage
@backoff.on_predicate(backoff.fibo,
lambda x: x is None,
max_value=13)
def poll_for_result(job_id):
result = check_job(job_id)
return result if result else None
Parameters
- wait_gen - Wait strategy generator (expo, fibo, constant, runtime)
- predicate - Function that returns True if retry is needed (default: falsey check)
- max_tries - Maximum number of attempts (default: None = unlimited)
- max_time - Maximum total time in seconds (default: None = unlimited)
- jitter - Jitter function to add randomness (default: full_jitter)
- on_success - Callback when predicate returns False
- on_backoff - Callback when predicate returns True
- on_giveup - Callback when giving up
- logger - Logger for retry events (default: 'backoff' logger)
Default Predicate (Falsey Check)
When no predicate is specified, the decorator retries on falsey values:
@backoff.on_predicate(backoff.constant, interval=2, max_time=60)
def wait_for_resource():
# Retries until a truthy value is returned
return resource.get() or None
Custom Predicates
Define specific conditions for retry:
@backoff.on_predicate(
backoff.expo,
lambda result: result["status"] == "pending",
max_time=600
)
def poll_job_status(job_id):
return api.get_job(job_id)
Combining Predicates
def needs_retry(result):
return (
result is None or
result.get("status") in ["pending", "processing"] or
not result.get("ready", False)
)
@backoff.on_predicate(backoff.fibo, needs_retry, max_value=60)
def complex_poll(resource_id):
return api.get_resource(resource_id)
Combining Decorators
Stack multiple decorators for complex retry logic:
@backoff.on_predicate(backoff.fibo, lambda x: x is None, max_value=13)
@backoff.on_exception(backoff.expo,
requests.exceptions.HTTPError,
max_time=60)
@backoff.on_exception(backoff.expo,
requests.exceptions.Timeout,
max_time=300)
def robust_poll(endpoint):
response = requests.get(endpoint)
response.raise_for_status()
data = response.json()
return data if data.get("ready") else None
The decorators are applied from bottom to top (inside out), so:
- Timeout exceptions get up to 300s of retries
- HTTP errors get up to 60s of retries
- None results trigger fibonacci backoff up to 13s
Event Handler Details
Event handlers receive a dictionary with these keys:
{
'target': <function reference>,
'args': <positional args tuple>,
'kwargs': <keyword args dict>,
'tries': <number of tries so far>,
'elapsed': <elapsed time in seconds>,
'wait': <seconds to wait>, # on_backoff only
'value': <return value>, # on_predicate only
'exception': <exception>, # on_exception only
}
Example handler:
def detailed_log(details):
print(f"Try {details['tries']}: "
f"elapsed={details['elapsed']:.2f}s, "
f"wait={details.get('wait', 0):.2f}s")
@backoff.on_exception(
backoff.expo,
Exception,
on_backoff=detailed_log,
max_tries=5
)
def my_function():
pass