Skip to main content

Policy-driven failure handling for Python services.

Project description

redress

CI codecov PyPI Version Docs Bench

redress (v.): to remedy or to set right.

Policy-driven failure handling for Python services.

redress treats retries, circuit breakers, and stop conditions as coordinated responses to classified failure—making failure behavior explicit, bounded, and observable.

Why redress?

Most failure-handling code grows organically around retries, circuit breakers, and ad-hoc rules. redress starts from a different premise: failure handling is policy.

Classify, then dispatch. Exceptions get mapped to semantic error classes (RATE_LIMIT, TRANSIENT, SERVER_ERROR, etc.), and each class can have its own backoff strategy. Rate limits back off aggressively; transient blips retry fast.

policy = Policy(
    retry=Retry(
        classifier=default_classifier,
        strategy=decorrelated_jitter(max_s=5.0),  # default fallback
        strategies={
            ErrorClass.RATE_LIMIT: decorrelated_jitter(max_s=60.0),
            ErrorClass.TRANSIENT: decorrelated_jitter(max_s=1.0),
        },
    ),
)

Single observability hook. One callback for success, retry, permanent failure, deadline exceeded—plug it into your metrics/logging and always know why retries stopped.

Circuit breaking. Policies can open a circuit after repeated failures, failing fast instead of piling up retries. Retries and circuit breakers are treated as policy responses to classified failure, not separate mechanisms.

Sync/async symmetry. Policy and AsyncPolicy share the same API and configuration; RetryPolicy / AsyncRetryPolicy remain convenient shortcuts.

SDK and framework integrations. First-class contrib modules for OpenAI and Anthropic, plus HTTP/framework integrations and thin extras for boto3, grpc, redis, urllib3, aiohttp, and pyodbc.

Retry budgets. Shared rolling-window limits to prevent retry storms across operations.

Documentation

Installation

From PyPI:

uv pip install redress
# or
pip install redress

Common optional installs:

uv pip install "redress[openai]"
uv pip install "redress[anthropic]"

Thin classifier extras for older stacks remain available separately, including redress[aiohttp], redress[grpc], redress[boto3], redress[redis], and redress[urllib3].

Quick Start

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

policy = Policy(
    retry=Retry(
        classifier=default_classifier,
        strategy=decorrelated_jitter(max_s=5.0),
        deadline_s=60,
        max_attempts=6,
    ),
    # Fail fast when the upstream is persistently unhealthy.
    circuit_breaker=CircuitBreaker(
        failure_threshold=5,
        window_s=60.0,
        recovery_timeout_s=30.0,
        trip_on={ErrorClass.SERVER_ERROR, ErrorClass.CONCURRENCY},
    ),
)

def flaky():
    # your operation that may fail
    ...

result = policy.call(flaky)

# RATE_LIMIT failures back off aggressively,
# TRANSIENT failures retry quickly,
# UNKNOWN failures are tightly capped.

Use Policy(retry=Retry(...)) as the default entry point. It is the canonical API for combining retries, circuit breakers, budgets, shared hooks, and structured outcomes.

Retry-only shortcuts

RetryPolicy is still available when you only need retry behavior:

from redress import RetryPolicy, default_classifier
from redress.strategies import decorrelated_jitter

policy = RetryPolicy(
    classifier=default_classifier,
    strategy=decorrelated_jitter(max_s=3.0),
)

The decorator is the smallest entry point:

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

@retry  # defaults to default_classifier + decorrelated_jitter(max_s=5.0)
def fetch_user():
    ...

# Or customize classifier/strategies
@retry(
    classifier=default_classifier,
    strategy=decorrelated_jitter(max_s=3.0),
)
def fetch_user_custom():
    ...

If you provide strategies without strategy, the decorator will not add a default strategy.

# Context manager for repeated calls with shared hooks/operation
with policy.context(operation="batch") as retry:
    retry(task1)
    retry(task2)

If you need circuit breakers, budgets, shared execution settings, or richer integration points, move up to Policy / AsyncPolicy.

Retry budget quick start

from redress import Budget, Policy, Retry, default_classifier
from redress.strategies import decorrelated_jitter

budget = Budget(max_retries=20, window_s=60.0)

policy = Policy(
    retry=Retry(
        classifier=default_classifier,
        strategy=decorrelated_jitter(max_s=2.0),
        budget=budget,
    ),
)

Use budgets when many callers or operations share the same unhealthy dependency and you want to bound aggregate retry volume, not just per-call retries.

Async quick start

import asyncio
from redress import AsyncPolicy, AsyncRetry, default_classifier
from redress.strategies import decorrelated_jitter

async_policy = AsyncPolicy(
    retry=AsyncRetry(
        classifier=default_classifier,
        strategy=decorrelated_jitter(max_s=5.0),
    ),
)

async def flaky_async():
    ...

asyncio.run(async_policy.call(flaky_async))

OpenAI / Anthropic SDKs

Use the provider contrib modules when you want redress to classify SDK-native exceptions and honor rate-limit headers without inventing a new error model. Disable SDK-native retries so redress owns attempt counting, deadlines, and metrics.

from openai import OpenAI
from redress import Policy, Retry
from redress.contrib.openai import openai_aware_backoff, openai_classifier

client = OpenAI(api_key="test", max_retries=0)

policy = Policy(
    retry=Retry(
        classifier=openai_classifier,
        strategy=openai_aware_backoff(max_s=30.0),
        max_attempts=6,
        deadline_s=120.0,
    ),
)

redress.contrib.anthropic follows the same shape with Anthropic(max_retries=0), anthropic_classifier, and anthropic_aware_backoff(...).

Choosing the API surface

  • Use Policy / AsyncPolicy by default.
  • Use RetryPolicy / AsyncRetryPolicy for retry-only convenience.
  • Use @retry when decorator ergonomics matter more than explicit policy objects.

Error Classes & Classification

AUTH
PERMISSION
PERMANENT
CONCURRENCY
RATE_LIMIT
SERVER_ERROR
TRANSIENT
UNKNOWN

Redress intentionally keeps ErrorClass small and fixed. The goal is semantic classification ("rate limit" vs. "server error") rather than mechanical mapping to every exception type. If you need finer-grained behavior, use separate policies per use case. Optional classification context can carry hints (for example, Retry-After) without expanding the class set.

Classification rules:

  • Explicit redress error types
  • Numeric codes (err.status or err.code)
  • Name heuristics
  • Fallback to UNKNOWN

Name heuristics are a convenience for quick starts; for production, prefer a domain-specific classifier (HTTP/DB/etc.) or strict_classifier to avoid surprises.

Classifiers can return Classification(klass=..., retry_after_s=..., details=...) to pass structured hints to strategies. Returning ErrorClass is shorthand for Classification(klass=klass).

Metrics & Observability

def metric_hook(event, attempt, sleep_s, tags):
    print(event, attempt, sleep_s, tags)

policy.call(my_op, on_metric=metric_hook)

Backoff Strategies

Strategy signature (context-aware):

(ctx: BackoffContext) -> float

Legacy signature (still supported):

(attempt: int, klass: ErrorClass, prev_sleep: Optional[float]) -> float

Built‑ins:

  • decorrelated_jitter()
  • equal_jitter()
  • token_backoff()
  • retry_after_or(...)
  • adaptive(...)

Per-Class Example

policy = Policy(
    retry=Retry(
        classifier=default_classifier,
        strategy=decorrelated_jitter(max_s=10.0),  # default
        strategies={
            ErrorClass.CONCURRENCY: decorrelated_jitter(max_s=1.0),
            ErrorClass.RATE_LIMIT: decorrelated_jitter(max_s=60.0),
            ErrorClass.SERVER_ERROR: equal_jitter(max_s=30.0),
        },
    ),
)

Deadline & Attempt Controls

policy = Policy(
    retry=Retry(
        classifier=default_classifier,
        strategy=decorrelated_jitter(),
        deadline_s=60,
        max_attempts=8,
        max_unknown_attempts=2,
    ),
)

Development

uv run pytest

CLI

  • Lint a retry config or policy to catch obvious misconfigurations:
# app_retry.py
from redress import RetryConfig
from redress.strategies import decorrelated_jitter

cfg = RetryConfig(
    default_strategy=decorrelated_jitter(max_s=1.5),
    max_attempts=5,
)

Then from the repo root or any env where app_retry is on PYTHONPATH:

redress doctor app_retry:cfg
# Show a normalized snapshot of active values:
redress doctor app_retry:cfg --show

doctor accepts module:attribute pointing to a RetryConfig, Policy/AsyncPolicy, Retry/AsyncRetry, or the RetryPolicy/AsyncRetryPolicy shortcuts. The attribute defaults to config if omitted (e.g., myapp.settings will look for settings:config).

Example --show output:

Config snapshot:
  source: app_retry:cfg
  deadline_s: 60.0
  max_attempts: 5
  max_unknown_attempts: 2
  default_strategy: redress.strategies.decorrelated_jitter.<locals>.f
  class_strategies:
    (none)
  per_class_max_attempts:
    (none)
OK: 'app_retry:cfg' passed config checks.

Examples (in docs/snippets/)

  • Sync httpx demo: uv pip install httpx then uv run python docs/snippets/httpx_sync_retry.py
  • Async httpx demo: uv pip install httpx then uv run python docs/snippets/httpx_async_retry.py
  • Async worker loop with retries: uv run python docs/snippets/async_worker_retry.py
  • Decorator usage (sync + async): uv run python docs/snippets/decorator_retry.py
  • FastAPI proxy with metrics counter: uv pip install "fastapi[standard]" httpx then uv run uvicorn docs.snippets.fastapi_downstream:app --reload
  • FastAPI middleware with per-endpoint policies: uv pip install "fastapi[standard]" httpx then uv run uvicorn docs.snippets.fastapi_middleware:app --reload
  • PyODBC + SQLSTATE classification example: uv pip install pyodbc then uv run python docs/snippets/pyodbc_retry.py
  • requests example: uv pip install requests then uv run python docs/snippets/requests_retry.py
  • asyncpg example: uv pip install asyncpg and set ASYNC_PG_DSN, then uv run python docs/snippets/asyncpg_retry.py
  • Pyperf microbenchmarks: uv pip install .[dev] then uv run python docs/snippets/bench_retry.py

Docs site

  • Build/serve locally: uv pip install .[docs] then uv run mkdocs serve
  • Pages: docs/index.md, docs/usage.md, docs/observability.md, docs/recipes.md with runnable snippets in docs/snippets/.

Versioning

Semantic Versioning.

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

redress-1.4.0.tar.gz (285.0 kB view details)

Uploaded Source

Built Distribution

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

redress-1.4.0-py3-none-any.whl (88.4 kB view details)

Uploaded Python 3

File details

Details for the file redress-1.4.0.tar.gz.

File metadata

  • Download URL: redress-1.4.0.tar.gz
  • Upload date:
  • Size: 285.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for redress-1.4.0.tar.gz
Algorithm Hash digest
SHA256 e965861d87615925af2689df9f07dc3c270cef7c6b294e576ba5f2f933ad197d
MD5 0190393dc5a0cd0bcdb363603e568860
BLAKE2b-256 305959b524ccbf3959a612a7d63cdc4155e2ce87c10e7fb43924ed47cec62ca1

See more details on using hashes here.

File details

Details for the file redress-1.4.0-py3-none-any.whl.

File metadata

  • Download URL: redress-1.4.0-py3-none-any.whl
  • Upload date:
  • Size: 88.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for redress-1.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 eef97b9e933efd8ac38a7a534a8a90edf223b38be35f5c6e212460668f5eae6f
MD5 8494dbc1d721568272c30eefc2c1f22e
BLAKE2b-256 524eb549d6eb99014fd4e7eadff2637a09c0bdecab3a80f4990f1ab23f4b0834

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