Observed since 20 Oct, 19:55 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
ad-tech-inc-resilient-http
A production-grade, asynchronous HTTP client for Python engineered to tolerate downstream service outages, network instability, and latency spikes.
Asynchronous HTTP client for Python engineered to tolerate downstream service outages, network instability, and latency spikes. It implements proven resilience patterns including Circuit Breakers, Retry Policies, Fallback Mechanisms, and a Distributed Failure Store to enable reliable service-to-service communication.
Built on top of httpx, the library is designed for modern distributed systems where resilience is a first-class requirement.
CLOSED, OPEN, HALF-OPEN) with advanced Count-based and Time-based sliding windows.httpx for high-concurrency, non-blocking I/O.The coordination of request delivery, state checking, retries, and fallback execution is modeled in our system architecture.
📊 View System Architecture Diagram 🕒 View Request Sequence Flow Diagram
The client implements a fully compliant circuit breaker state machine with lazy cooldown transitions and probe request gating.
Requests flow normally.
Requests fail immediately without contacting the downstream service.
Allows a limited number of probe requests.
Install the package via pip or your favorite package manager:
pip install ad-tech-inc-resilient-http
Or using uv:
uv add ad-tech-inc-resilient-http
import asyncio
import redis.asyncio as redis
from resilient_http_client import (
FailureStore,
ResilientHttpClient,
)
async def main():
# Example using Redis-backed storage
redis_client = redis.Redis(
host="localhost",
port=6379,
decode_responses=True,
)
store = FailureStore(
redis=redis_client,
service="stripe_payment",
)
async with ResilientHttpClient(
service="stripe_payment",
store=store,
) as client:
response = await client.request(
method="POST",
url="https://api.stripe.com/v1/charges",
json={
"amount": 2000,
"currency": "usd",
},
)
# The request returns a raw httpx.Response object on success
if hasattr(response, "json"):
print("Status:", response.status_code)
print("Response Data:", response.json())
else:
# Fallback values returned as a dict
print("Fallback Response:", response)
if __name__ == "__main__":
asyncio.run(main())
Customize resilience behavior through ResilienceConfig.
from resilient_http_client import ResilienceConfig
config = ResilienceConfig(
cooldown=30,
max_retries=3,
timeout=5.0,
half_open_max_calls=3,
half_open_successes_needed=2,
# Sliding window configuration
sliding_window_type="COUNT_BASED", # "COUNT_BASED" or "TIME_BASED"
sliding_window_size=10, # Evaluate last 10 requests or last 10 seconds
minimum_number_of_calls=5, # Do not trip until at least 5 calls are made
failure_rate_threshold=50.0, # Trip open if >= 50.0% of requests in window fail
# Status codes customization
retry_status_codes={408, 429, 500, 503}, # Status codes that trigger retries
circuit_failure_status_codes={500, 503}, # Status codes that count as circuit breaker failures
# Retry delay customization
retry_backoff_base=0.1, # Starting backoff delay in seconds
retry_max_delay=10.0, # Maximum backoff delay cap in seconds
)
client = ResilientHttpClient(
service="my-api",
store=store,
config=config,
)
You can override max_retries, timeout, fallback, or ignore_circuit directly on individual request calls without altering global client configuration:
response = await client.request(
method="POST",
url="https://api.stripe.com/v1/charges",
json={"amount": 2000, "currency": "usd"},
max_retries=5, # Custom retries for high-priority request
timeout=2.0, # Tight timeout override in seconds
fallback=lambda err: {"degraded": True}, # Custom fallback callback
ignore_circuit=False, # Bypass circuit breaker check if True
)
| Parameter | Type | Default | Description |
|---|---|---|---|
cooldown | int | 30 | Seconds before an open circuit transitions to half-open |
max_retries | int | 3 | Number of retry attempts before failure |
timeout | float | 5.0 | Request timeout in seconds |
half_open_max_calls | int | 1 | Maximum probe requests allowed while half-open |
half_open_successes_needed | int | 1 | Successful probes required to close the circuit |
sliding_window_type | str | "COUNT_BASED" | Type of sliding window: "COUNT_BASED" or "TIME_BASED" |
sliding_window_size | int | 10 | Size of sliding window: number of calls (count-based) or number of seconds (time-based) |
minimum_number_of_calls | int | 5 | Minimum calls recorded in the window before failure rate percentage is evaluated |
failure_rate_threshold | float | 50.0 | Percentage of failures in the window required to trip the circuit open |
retry_status_codes | Set[int] | {408, 429, 500, 502, 503, 504} | Set of HTTP status codes that trigger a retry attempt |
circuit_failure_status_codes | Set[int] | {500, 502, 503, 504} | Set of HTTP status codes that count as circuit breaker failures |
retry_backoff_base | float | 0.1 | Starting backoff base delay in seconds for exponential backoff |
retry_max_delay | float | 10.0 | Maximum delay cap in seconds for retry attempts |
failure_threshold | int | 5 | (Deprecated/Fallback) Absolute consecutive failures required to open the circuit |
Prevents repeated requests to unhealthy downstream services.
States:
Automatically retries transient failures using configurable exponential backoff.
Typical retry conditions include:
Fallbacks are executed when:
All core resilience components satisfy runtime-checkable Python Protocol interfaces. You can easily plug in custom implementations or type-hint dependencies:
FailureStoreProtocol — Custom state backends (Redis, Memcached, DynamoDB).HttpExecutorProtocol — Custom HTTP transports or mock executors.RetryPolicyProtocol — Custom backoff strategies (jitter, linear, fixed).FallbackHandlerProtocol — Custom fallback execution logic.from resilient_http_client import (
FailureStoreProtocol,
HttpExecutorProtocol,
RetryPolicyProtocol,
FallbackHandlerProtocol,
)
Persists resilience state used by the circuit breaker.
Responsibilities include:
The library includes a Redis-backed implementation and can be extended with custom storage backends.
src/
└── resilient_http_client/
├── __init__.py
├── circuit_breaker.py
├── client.py
├── config.py
├── failure_store.py
├── fallback.py
├── http.py
├── protocols/
│ ├── __init__.py
│ ├── failure_store_protocol.py
│ ├── fallback_handler_protocol.py
│ ├── http_executor_protocol.py
│ └── retry_policy_protocol.py
├── retry.py
└── types.py
tests/
├── test_circuit_breaker.py
├── test_failure_store.py
├── test_fallback.py
├── test_flaky_resilience.py
├── test_integration.py
├── test_protocols.py
└── test_retry_policy.py
client.py — Main orchestration layer.circuit_breaker.py — Circuit breaker state machine.failure_store.py — Distributed state management.retry.py — Retry policy implementation.fallback.py — Fallback registration and execution.config.py — Configuration definitions.types.py — Shared enums and type definitions.http.py — HTTP request execution layer.Run the full test suite:
uv run pytest
The examples/ directory contains complete demonstrations and integrations.
Start Redis:
docker compose up -d
Run the example:
PYTHONPATH=. uv run python examples/slack_example.py
Available examples:
fastapi_simple.pyfastapi_custom.pysimulate_outage.pyslack_example.pystripe_example.pyDownload counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
fastapiEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.12",
"evaluation": "NOT_EVALUATED"
} | fastapi>=0.137.1 | runtime |
httpxEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.12",
"evaluation": "NOT_EVALUATED"
} | httpx>=0.28.1 | runtime |
pytest-asyncioEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.12",
"evaluation": "NOT_EVALUATED"
} | pytest-asyncio>=1.4.0 | runtime |
pytestEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.12",
"evaluation": "NOT_EVALUATED"
} | pytest>=9.0.3 | runtime |
redisEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.12",
"evaluation": "NOT_EVALUATED"
} | redis>=8.0.0 | runtime |
ruffEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.12",
"evaluation": "NOT_EVALUATED"
} | ruff>=0.15.21 | runtime |
bdist_wheel · 16.21 KB · Python py3
sdist · 576.05 KB · Python source
Observed since 20 Oct, 19:55 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:a8dbaca5c62e0c0a255a871f371dc574c61bb88b0e75e83cde882e7e617b41b3