philiprehberger-circuit-breaker
Circuit breaker pattern for fault-tolerant service calls.
Installation
pip install philiprehberger-circuit-breaker
Usage
from philiprehberger_circuit_breaker import circuit_breaker
@circuit_breaker(failure_threshold=3, recovery_timeout=60)
def call_external_service():
return requests.get("https://api.example.com/data").json()
result = call_external_service()
Class-Based Usage
from philiprehberger_circuit_breaker import CircuitBreaker
breaker = CircuitBreaker(failure_threshold=5, recovery_timeout=30)
result = breaker.call(requests.get, "https://api.example.com/data")
Handling Open Circuits
from philiprehberger_circuit_breaker import CircuitOpenError, circuit_breaker
@circuit_breaker(failure_threshold=3)
def fetch_data():
return requests.get("https://api.example.com").json()
try:
result = fetch_data()
except CircuitOpenError:
result = cached_fallback()
Checking Circuit State
from philiprehberger_circuit_breaker import CircuitBreaker, CircuitState
breaker = CircuitBreaker(failure_threshold=3)
if breaker.get_state() is CircuitState.OPEN:
print("Circuit is open, using fallback")
Observability with get_stats
from philiprehberger_circuit_breaker import CircuitBreaker
breaker = CircuitBreaker(failure_threshold=5, recovery_timeout=30)
stats = breaker.get_stats()
print(f"State: {stats.state.value}")
print(f"Failures: {stats.failure_count}")
print(f"Successes: {stats.success_count}")
print(f"Last failure: {stats.last_failure_time}")
print(f"Recovery timeout: {stats.current_recovery_timeout}s")
State Transition Callbacks
Register callbacks to be notified when the circuit changes state.
from philiprehberger_circuit_breaker import CircuitBreaker
def notify_open():
print("Circuit opened!")
breaker = CircuitBreaker(
failure_threshold=3,
on_open=notify_open,
on_close=lambda: print("Circuit closed."),
on_half_open=lambda: print("Circuit half-open."),
)
result = breaker.call(requests.get, "https://api.example.com/data")
Event Listeners
Register multiple callbacks for the same state transition event.
from philiprehberger_circuit_breaker import CircuitBreaker
breaker = CircuitBreaker(failure_threshold=3)
breaker.add_listener("on_open", lambda: print("Listener 1: opened"))
breaker.add_listener("on_open", lambda: print("Listener 2: opened"))
breaker.add_listener("on_close", lambda: print("Service recovered"))
# Remove a listener when no longer needed
def my_handler():
print("temporary handler")
breaker.add_listener("on_open", my_handler)
breaker.remove_listener("on_open", my_handler)
Per-Exception-Type Failure Thresholds
Use ExceptionFilter to configure which exceptions count as failures and set independent thresholds per exception type.
from philiprehberger_circuit_breaker import CircuitBreaker, ExceptionFilter
exc_filter = ExceptionFilter(
base_exceptions=(ConnectionError, TimeoutError, OSError),
thresholds={TimeoutError: 2},
)
breaker = CircuitBreaker(failure_threshold=5, exception_filter=exc_filter)
result = breaker.call(requests.get, "https://api.example.com/data")
Half-Open Probe Limiting
Control how many test calls are allowed in the half-open state before requiring a success.
from philiprehberger_circuit_breaker import CircuitBreaker
breaker = CircuitBreaker(
failure_threshold=3,
recovery_timeout=30,
half_open_max_calls=3,
)
Health Window
Track success rate over a rolling time window instead of relying solely on consecutive failures.
from philiprehberger_circuit_breaker import CircuitBreaker, HealthWindow
health_window = HealthWindow(
window_size=60.0,
failure_rate_threshold=0.5,
min_calls=10,
)
breaker = CircuitBreaker(
failure_threshold=100,
health_window=health_window,
)
result = breaker.call(requests.get, "https://api.example.com/data")
Exponential Backoff on Recovery Timeout
Instead of a fixed recovery timeout, the timeout can increase exponentially with consecutive circuit trips.
from philiprehberger_circuit_breaker import CircuitBreaker
breaker = CircuitBreaker(
failure_threshold=3,
recovery_timeout=10,
backoff_multiplier=2.0,
max_recovery_timeout=300.0,
)
Resetting the Circuit
from philiprehberger_circuit_breaker import circuit_breaker
@circuit_breaker(failure_threshold=3)
def my_service_call():
...
my_service_call.breaker.reset()
Async usage with acall
import httpx
from philiprehberger_circuit_breaker import CircuitBreaker
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30)
async def fetch_user(user_id: int) -> dict:
async with httpx.AsyncClient() as client:
resp = await client.get(f"https://api.example.com/users/{user_id}")
resp.raise_for_status()
return resp.json()
# Awaits the coroutine through the same lock and state machine as call()
user = await breaker.acall(fetch_user, 42)
API
CircuitBreaker
| Function / Class | Description |
|---|---|
CircuitBreaker(failure_threshold, recovery_timeout, expected_exceptions, *, on_open, on_close, on_half_open, exception_filter, backoff_multiplier, max_recovery_timeout, half_open_max_calls, health_window) |
Create a circuit breaker instance |
call(fn, *args, **kwargs) |
Execute a function through the circuit breaker |
acall(fn, *args, **kwargs) |
Async counterpart to call() — awaits a coroutine function |
state |
Current circuit state (CLOSED, OPEN, or HALF_OPEN) |
get_state() |
Return the current circuit state |
get_stats() |
Return a CircuitBreakerStats snapshot |
add_listener(event, callback) |
Register a callback for a state transition event |
remove_listener(event, callback) |
Remove a previously registered callback |
reset() |
Reset the circuit breaker to the closed state |
CircuitBreakerStats
| Function / Class | Description |
|---|---|
state |
Current circuit state |
failure_count |
Total failure count |
success_count |
Total success count |
last_failure_time |
Monotonic timestamp of the last failure, or None |
consecutive_opens |
Number of consecutive times the circuit has opened |
current_recovery_timeout |
Current recovery timeout in seconds |
health_window_failure_rate |
Failure rate from the health window, or None |
CircuitState
| Function / Class | Description |
|---|---|
CLOSED |
Normal operation, calls pass through |
OPEN |
Circuit tripped, calls are rejected |
HALF_OPEN |
Recovery probe, next call determines transition |
CircuitOpenError
| Function / Class | Description |
|---|---|
breaker |
Reference to the CircuitBreaker that raised the error |
ExceptionFilter
| Function / Class | Description |
|---|---|
ExceptionFilter(base_exceptions, thresholds) |
Create an exception filter with optional per-type thresholds |
matches(exc) |
Return True if the exception counts as a failure |
record(exc) |
Record a failure; returns True if a per-type threshold was reached |
reset() |
Reset all per-type counters |
HealthWindow
| Function / Class | Description |
|---|---|
HealthWindow(window_size, failure_rate_threshold, min_calls) |
Create a rolling health window |
record_success(now) |
Record a successful call |
record_failure(now) |
Record a failed call |
should_open(now) |
Return True if failure rate exceeds threshold |
failure_rate(now) |
Return current failure rate (0.0 to 1.0) |
reset() |
Clear all recorded calls |
circuit_breaker
| Function / Class | Description |
|---|---|
circuit_breaker(failure_threshold, recovery_timeout, expected_exceptions, *, on_open, on_close, on_half_open, exception_filter, backoff_multiplier, max_recovery_timeout, half_open_max_calls, health_window) |
Decorator factory that wraps a function with a CircuitBreaker |
Development
pip install -e .
python -m pytest tests/ -v
Support
If you find this project useful:
License
Metadata
Release files for philiprehberger-circuit-breaker 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| philiprehberger_circuit_breaker-0.4.0.tar.gz | 185.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| philiprehberger_circuit_breaker-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 195.6 kB
Release files / philiprehberger_circuit_breaker-0.4.0.tar.gz
| Download URL | philiprehberger_circuit_breaker-0.4.0.tar.gz |
|---|---|
| Size | 185.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
876fd3d1044f8f8bd070b34688f9bcc7126c0833dd9b60dc00d21873714eb3c0
|
|
BLAKE2b-256 checksum How to use checksums |
4c07d82c3860569ae939628e95b6b428071b820f6be7a1d511c020a5c4b24ab9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / philiprehberger_circuit_breaker-0.4.0-py3-none-any.whl
| Download URL | philiprehberger_circuit_breaker-0.4.0-py3-none-any.whl |
|---|---|
| Size | 9.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bd621a6be220abf277e0eb1cfeae7772833bc1cb0a87770d7292411746d55146
|
|
BLAKE2b-256 checksum How to use checksums |
be3db0ef26636a406c05a11deac0f05ffec7d2f045bc536803019949981d3768
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Security for philiprehberger-circuit-breaker 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.