Python implementation of the Circuit Breaker pattern for handling failing subsystems gracefully
—
PyBreaker supports different storage backends for persisting circuit breaker state. This enables circuit breakers to maintain state across application restarts and coordinate state in distributed systems.
In-memory storage for single-process applications where circuit state doesn't need to persist across restarts.
class CircuitMemoryStorage:
def __init__(self, state: str):
"""
Create a new in-memory storage instance.
Args:
state (str): Initial circuit state ('open', 'closed', 'half-open')
"""
@property
def state(self) -> str:
"""Current circuit breaker state."""
@state.setter
def state(self, state: str) -> None:
"""Set the current circuit breaker state."""
@property
def counter(self) -> int:
"""Current failure counter value."""
@property
def success_counter(self) -> int:
"""Current success counter value."""
@property
def opened_at(self) -> datetime | None:
"""Datetime when circuit was last opened."""
@opened_at.setter
def opened_at(self, datetime: datetime) -> None:
"""Set when circuit was opened."""
def increment_counter(self) -> None:
"""Increment the failure counter by one."""
def reset_counter(self) -> None:
"""Reset the failure counter to zero."""
def increment_success_counter(self) -> None:
"""Increment the success counter by one."""
def reset_success_counter(self) -> None:
"""Reset the success counter to zero."""Redis-backed storage for distributed applications where circuit state needs to be shared across multiple processes or servers.
class CircuitRedisStorage:
def __init__(self,
state: str,
redis_object: Redis,
namespace: str | None = None,
fallback_circuit_state: str = "closed",
cluster_mode: bool = False):
"""
Create a new Redis storage instance.
Args:
state (str): Initial circuit state
redis_object (Redis): Redis client instance (e.g., redis.Redis())
namespace (str | None): Optional namespace for Redis keys
fallback_circuit_state (str): State to use when Redis is unavailable
cluster_mode (bool): Enable Redis cluster mode support
Note:
Requires 'redis' package to be installed.
Do not use decode_responses=True in Redis client.
Check pybreaker.HAS_REDIS_SUPPORT to verify availability.
"""
@property
def state(self) -> str:
"""
Current circuit breaker state from Redis.
Falls back to fallback_circuit_state on Redis errors.
"""
@state.setter
def state(self, state: str) -> None:
"""Set the current circuit breaker state in Redis."""
@property
def counter(self) -> int:
"""Current failure counter value from Redis."""
@property
def success_counter(self) -> int:
"""Current success counter value from Redis."""
@property
def opened_at(self) -> datetime | None:
"""Datetime when circuit was last opened, from Redis."""
@opened_at.setter
def opened_at(self, datetime: datetime) -> None:
"""Atomically set when circuit was opened in Redis."""
def increment_counter(self) -> None:
"""Increment the failure counter in Redis."""
def reset_counter(self) -> None:
"""Reset the failure counter to zero in Redis."""
def increment_success_counter(self) -> None:
"""Increment the success counter in Redis."""
def reset_success_counter(self) -> None:
"""Reset the success counter to zero in Redis."""Abstract base class defining the storage interface for custom storage implementations. All concrete storage classes must inherit from this class and implement the abstract methods.
class CircuitBreakerStorage:
def __init__(self, name: str) -> None:
"""
Create a new storage instance.
Args:
name (str): Human-friendly name for this storage type
"""
@property
def name(self) -> str:
"""Human friendly name that identifies this storage type."""
@property
def state(self) -> str:
"""Override this method to retrieve the current circuit breaker state."""
@state.setter
def state(self, state: str) -> None:
"""Override this method to set the current circuit breaker state."""
def increment_counter(self) -> None:
"""Override this method to increase the failure counter by one."""
def reset_counter(self) -> None:
"""Override this method to set the failure counter to zero."""
def increment_success_counter(self) -> None:
"""Override this method to increase the success counter by one."""
def reset_success_counter(self) -> None:
"""Override this method to set the success counter to zero."""
@property
def counter(self) -> int:
"""Override this method to retrieve the current failure counter value."""
@property
def success_counter(self) -> int:
"""Override this method to retrieve the current success counter value."""
@property
def opened_at(self) -> datetime | None:
"""Override this method to retrieve when the circuit was opened."""
@opened_at.setter
def opened_at(self, datetime: datetime) -> None:
"""Override this method to set when the circuit was opened."""import pybreaker
# Memory storage is used by default
breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=60)
# Explicitly specify memory storage
storage = pybreaker.CircuitMemoryStorage(state=pybreaker.STATE_CLOSED)
breaker = pybreaker.CircuitBreaker(state_storage=storage)import pybreaker
import redis
# Create Redis client
redis_client = redis.Redis(host='localhost', port=6379, db=0)
# Create Redis storage
storage = pybreaker.CircuitRedisStorage(
state=pybreaker.STATE_CLOSED,
redis_object=redis_client,
namespace="myapp", # Optional namespace for keys
fallback_circuit_state=pybreaker.STATE_CLOSED
)
# Create circuit breaker with Redis storage
breaker = pybreaker.CircuitBreaker(
fail_max=5,
reset_timeout=60,
state_storage=storage,
name="user_service"
)import pybreaker
from django_redis import get_redis_connection
# Use existing Django Redis connection
redis_client = get_redis_connection('default')
storage = pybreaker.CircuitRedisStorage(
state=pybreaker.STATE_CLOSED,
redis_object=redis_client,
namespace="circuit_breakers"
)
breaker = pybreaker.CircuitBreaker(state_storage=storage)import pybreaker
import redis
redis_client = redis.Redis(host='localhost', port=6379, db=0)
# Each service gets its own namespace
user_storage = pybreaker.CircuitRedisStorage(
state=pybreaker.STATE_CLOSED,
redis_object=redis_client,
namespace="user_service"
)
payment_storage = pybreaker.CircuitRedisStorage(
state=pybreaker.STATE_CLOSED,
redis_object=redis_client,
namespace="payment_service"
)
user_breaker = pybreaker.CircuitBreaker(state_storage=user_storage)
payment_breaker = pybreaker.CircuitBreaker(state_storage=payment_storage)import pybreaker
import redis
# For Redis cluster deployments
redis_client = redis.Redis(host='cluster-endpoint', port=6379)
storage = pybreaker.CircuitRedisStorage(
state=pybreaker.STATE_CLOSED,
redis_object=redis_client,
cluster_mode=True # Enable cluster mode
)
breaker = pybreaker.CircuitBreaker(state_storage=storage)import pybreaker
import sqlite3
from datetime import datetime
class SQLiteStorage(pybreaker.CircuitBreakerStorage):
def __init__(self, db_path, circuit_name):
super().__init__("sqlite")
self.db_path = db_path
self.circuit_name = circuit_name
self._init_db()
def _init_db(self):
# Initialize SQLite database and tables
pass
@property
def state(self):
# Implement state retrieval from SQLite
pass
@state.setter
def state(self, state):
# Implement state storage to SQLite
pass
# Implement other required methods...
# Use custom storage
storage = SQLiteStorage("/path/to/circuit.db", "user_service")
breaker = pybreaker.CircuitBreaker(state_storage=storage)Install with Tessl CLI
npx tessl i tessl/pypi-pybreaker