PyBreaker
PyBreaker is a Python implementation of the Circuit Breaker pattern, a resilience mechanism described in Michael T. Nygard's book 'Release It!'. It prevents cascading failures in distributed systems by detecting unhealthy services and temporarily stopping requests to them. The library provides configurable failure thresholds, reset timeouts, and optional Redis backing for state management. It is actively maintained, with the current version being 1.4.1, and receives regular updates.
Warnings
- breaking Python 3.8 support was dropped in PyBreaker v1.3.0, and Python 3.7 support was dropped in v1.2.0. Users on these older Python versions must upgrade to Python 3.9+ to use newer PyBreaker versions.
- deprecated PyBreaker v1.3.0 migrated from `datetime.datetime.utcnow()` to `datetime.datetime.now(UTC)` due to `utcnow()` being deprecated in Python 3.12. While `pybreaker` itself handles this, users with custom listeners or state management using `utcnow()` might encounter `DeprecationWarning`s or unexpected behavior with naive datetimes in newer Python versions.
- gotcha When using `CircuitRedisStorage`, do NOT initialize the `redis.StrictRedis` (or `redis.Redis`) connection with `decode_responses=True`. This will cause `AttributeError: 'str' object has no attribute 'decode'` in Python 3+ when `pybreaker` attempts to decode state values.
- gotcha For distributed applications with multiple instances of your service, a `CircuitBreaker` instance must use shared state storage (e.g., `CircuitRedisStorage`) to ensure all instances observe the same circuit state. Without shared storage, each instance will manage its own circuit independently, defeating the purpose of a distributed circuit breaker.
- gotcha Incorrectly configuring `fail_max`, `reset_timeout`, or `success_threshold` can lead to circuits tripping too easily (false positives) or failing to open quickly enough, exacerbating cascading failures.
Install
-
pip install pybreaker -
pip install pybreaker[redis]
Imports
- CircuitBreaker
from pybreaker import CircuitBreaker
- CircuitRedisStorage
from pybreaker import CircuitRedisStorage
- CircuitBreakerError
from pybreaker import CircuitBreakerError
Quickstart
import pybreaker
import requests
# Simulate a flaky external service
_service_up = True
def external_service_call():
global _service_up
if _service_up:
print("Calling external service...")
# Simulate a network error or service unavailability
if requests.get("http://localhost:9999/health", timeout=0.1).status_code != 200:
raise requests.exceptions.ConnectionError("Service not reachable")
return "Service data"
else:
print("Service is intentionally down (simulated).")
raise requests.exceptions.ConnectionError("Service is down")
# Create a circuit breaker instance
# Opens after 3 failures within 60 seconds
# Stays open for 10 seconds before trying again (half-open)
my_breaker = pybreaker.CircuitBreaker(
fail_max=3,
reset_timeout=10,
exclude=[requests.exceptions.HTTPError] # Exclude HTTP errors that are not system failures
)
@my_breaker
def protected_call():
return external_service_call()
print("--- Starting Circuit Breaker Demo ---")
for i in range(10):
print(f"\nAttempt {i+1}:")
try:
result = protected_call()
print(f"Success: {result}")
_service_up = True # Reset service state on success to show circuit closing
except pybreaker.CircuitBreakerError:
print("Circuit OPEN! Falling back to cached data or default.")
_service_up = False # Keep service down if breaker opened
except requests.exceptions.ConnectionError as e:
print(f"Connection error: {e}. Circuit state: {my_breaker.current_state}")
# Simulate service coming back up after a few failures
if i == 5: # Make service available after 5 attempts
_service_up = True
except Exception as e:
print(f"Unexpected error: {e}")
# A real application would not typically control _service_up like this in quickstart,
# but it illustrates the breaker's behavior.