aiobreaker: Circuit Breaker for asyncio
aiobreaker is a Python implementation of the Circuit Breaker pattern, specifically designed for asynchronous applications using `asyncio`. It helps improve the resilience of microservices by preventing an application from repeatedly trying to execute an operation that is likely to fail, such as calling a service that is temporarily down. The current version is 1.2.0, with a stable, infrequent release cadence reflecting its focused scope.
Common errors
-
aiobreaker.errors.CircuitBreakerError: CircuitBreaker is open
cause The decorated asynchronous function failed a configured number of times (`fail_max`), causing the circuit breaker to transition to an OPEN state, blocking further calls.fixThis is expected behavior. Implement a fallback mechanism or error handling in your `except CircuitBreakerError:` block. Monitor the underlying service to understand why it is failing. -
TypeError: 'coroutine' object is not callable
cause You are attempting to call a function decorated with `@breaker` without `await`ing it, or the decorated function itself is not an `async def`.fixEnsure the function decorated with `@breaker` is defined as `async def` and always invoke it with `await function_name(...)`. -
NameError: name 'CircuitBreaker' is not defined
cause The `CircuitBreaker` class was not correctly imported into your Python file.fixAdd `from aiobreaker import CircuitBreaker` at the top of your script or module.
Warnings
- gotcha Failing to handle `aiobreaker.errors.CircuitBreakerError` can lead to unhandled exceptions when the circuit breaker opens. The purpose of a circuit breaker is to allow for graceful degradation, not just to block calls.
- gotcha `aiobreaker` is designed exclusively for `asyncio` applications. Using it with synchronous functions will lead to unexpected behavior or runtime errors, as it expects awaitable callables.
- gotcha Incorrectly configuring `fail_max` or `timeout_duration` can render the circuit breaker ineffective. Too low `fail_max` might trip it unnecessarily; too high might not protect against failing services. Incorrect `timeout_duration` might keep the service unavailable too long or try too soon.
- gotcha The `exclude` parameter in `CircuitBreaker` allows specifying exceptions that *should not* trip the breaker. Misusing this (e.g., excluding critical errors or including transient ones) can lead to the breaker either never opening or opening too aggressively.
Install
-
pip install aiobreaker
Imports
- CircuitBreaker
from aiobreaker import CircuitBreaker
- CircuitBreakerError
from aiobreaker import CircuitBreakerError
- CircuitBreakerMonitor
from aiobreaker import CircuitBreakerMonitor
- CircuitBreakerState
from aiobreaker.state import CircuitBreakerState
Quickstart
import asyncio
from aiobreaker import CircuitBreaker, CircuitBreakerError
# Configure a Circuit Breaker: open after 3 failures, stay open for 5 seconds
breaker = CircuitBreaker(fail_max=3, timeout_duration=5)
# Simulate an unreliable asynchronous service
failure_count = 0
@breaker
async def unreliable_service():
global failure_count
if failure_count < 4: # Intentionally fail 4 times to trip the breaker (fail_max=3)
failure_count += 1
print(f"Service attempt failed ({failure_count}/{breaker.fail_max})")
raise ConnectionError("Simulated network issue")
print("Service successful (after breaker resets/trips)")
return "Data retrieved"
async def main():
print("\n--- Starting service calls ---")
for i in range(12):
print(f"\nCall {i+1}:")
try:
result = await unreliable_service()
print(f"Call successful: {result}")
except CircuitBreakerError:
print("CircuitBreaker is OPEN! Blocking service calls to prevent further load.")
except ConnectionError as e:
print(f"Call failed with transient error: {e}")
await asyncio.sleep(1)
print("\n--- All calls attempted ---")
if __name__ == "__main__":
asyncio.run(main())