Skip to content

Migrating from Backoff

Keep an inventory of exception filters, result predicates, give-up rules, wait schedules, and terminal behavior before replacing decorators.

Map the configuration

Backoff configuration Redress configuration
on_exception(..., ExceptionType) Classifier selecting those exceptions
max_tries=n max_attempts=n, including the initial call
max_time=t deadline_s=t; neither should replace client timeouts
giveup=predicate Classify matching exceptions as PERMANENT
on_predicate(...) result_classifier returning a retryable class for matching results
expo and jitter Choose a Redress strategy and verify the resulting delays
on_backoff, on_success, on_giveup Adapt observability hooks and terminal outcome handling
raise_on_giveup=False Explicit application fallback after a failed execute()

Consult the Backoff documentation for the source options. Redress callbacks and strategy functions have different signatures; copying them unchanged will not preserve behavior.

Translate exception filtering

Before:

import backoff

@backoff.on_exception(backoff.expo, TimeoutError, max_tries=5, max_time=30)
def fetch_user():
    return "user"  # Replace with the existing operation.

After (standalone and runnable):

from redress import ErrorClass, retry
from redress.strategies import decorrelated_jitter

def classify_timeout(exc):
    if isinstance(exc, TimeoutError):
        return ErrorClass.TRANSIENT
    return ErrorClass.PERMANENT

@retry(
    classifier=classify_timeout,
    strategy=decorrelated_jitter(base_s=0.25, max_s=5.0),
    max_attempts=5,
    deadline_s=30.0,
)
def fetch_user():
    return "user"  # Replace with the same operation.

assert fetch_user() == "user"

This preserves exception selection and the total attempt ceiling. It deliberately changes the wait distribution to decorrelated jitter. If exact timing matters, implement a strategy using BackoffContext.attempt (1-based) and test the delay sequence. See Retry strategies.

Do not replace a narrow exception filter with default_classifier without reviewing the change. Its fallback is UNKNOWN; the default max_unknown_attempts=2 stops on the third unknown failure. An explicit classifier avoids unexpected early termination or retries of excluded errors. For a give-up rule, return PERMANENT before selecting a retryable class.

Translate result predicates and fallback behavior

from redress import ErrorClass, Policy, Retry
from redress.strategies import decorrelated_jitter

policy = Policy(retry=Retry(
    classifier=lambda exc: ErrorClass.PERMANENT,
    result_classifier=lambda value: ErrorClass.TRANSIENT if not value else None,
    strategy=decorrelated_jitter(base_s=0.1, max_s=1.0),
    max_attempts=3,
    deadline_s=5.0,
))

outcome = policy.execute(lambda: [], operation="poll_jobs")
assert not outcome.ok
assert outcome.last_result == []
jobs = outcome.value if outcome.ok else []  # Application-owned fallback.

Return None from the result classifier for success; return ErrorClass or Classification for failure. A boolean predicate alone is not a classifier. The example retries falsey results and stops immediately on exceptions.

call() re-raises ordinary terminal operation exceptions. For result-based terminal failure it raises RetryExhaustedError, rather than returning the last unsatisfactory result. Use execute() and inspect stop_reason, last_result, and last_exception when preserving a fallback or give-up contract.

Async, handlers, and Retry-After

Redress @retry supports async def; await the decorated operation. Explicit async policies use AsyncPolicy with AsyncRetry, and their execution methods must be awaited. Keep client operations asynchronous.

Replace handler dictionaries with the documented hook signatures: on_metric(event, attempt, sleep_s, tags) or on_log(event, fields). Use on_attempt_start(ctx) / on_attempt_end(ctx) for attempt context and execute() for terminal application decisions. Metric and log hook exceptions are swallowed, so test backend adapters independently.

For HTTP Retry-After, use structured classification with retry_after_s and retry_after_or(...); see the Retry-After starter. A retry deadline may truncate the selected sleep. It does not interrupt a running request; configure client timeouts as described in Performance tuning.

Verify before switching traffic

Test success, an allowed exception followed by recovery, give-up exceptions, exhaustion, falsey results, fallback behavior, and async cancellation where used. Assert counts and delay sequences with testing utilities. Avoid nesting the old decorator and the new policy: retries at both layers can multiply downstream calls. See the migration overview for the path to shared budgets and circuit breakers.