Package: pycircuitbreaker
PyCircuitBreakerPython Implementation of theCircuit Breaker Pattern. Inspired bycircuitbreakerby Fabian Fuelling.Installationpip install pycircuitbreakerUsageThe simplest usage ofpycircuitbreakeris to decorate a function that can fail usingcircuit.frompycircuitbreakerimportcircuit@circuitdeffunction_that_can_fail():...For readiness probes (PaaS, k8s, ...) it is common to expose the different circuit breakers state.frompycircuitbreakerimportcircuit,CircuitBreakerRegistryregistry=CircuitBreakerRegistry()@app.route("/ready")defready():content={"circuits":{cb.id:cb.state.nameforcbinregistry.get_circuits()}}status=(200iflen(list(registry.get_open_circuits()))==0else500)returncontent,status,{"Cache-Control":"no-cache"}Note that the registry is not automatically managed by the library, it is the application responsibility to register created circuit breakers.It is also possible to reuse the same circuit breaker for different functions that rely on the same external dependency.defdb_breaker(func:Callable)->Callable:breaker=CircuitBreaker(breaker_id="db",error_threshold=5,recovery_timeout=30,recovery_threshold=1,exception_denylist=[DisconnectionError,TimeoutError],)@wraps(func)defwrapper(*args:Any,**kwargs:Any)->Any:returnbreaker.call(func,*args,**kwargs)returncircuit_wrapper@db_breakerdefcall_to_db():...@db_breakerdefanother_call_to_db():...Complete usage examplefrompycircuitbreakerimportCircuitBreakerRegistry,CircuitBreakerregistry=CircuitBreakerRegistry()@app.route("/ready")defready():content={"circuits":{cb.id:cb.state.nameforcbinregistry.get_circuits()}}status=(200iflen(list(registry.get_open_circuits()))==0else500)returncontent,status,{"Cache-Control":"no-cache"}defdb_breaker(func:Callable)->Callable:breaker=CircuitBreaker(breaker_id="db",error_threshold=5,recovery_timeout=30,recovery_threshold=1,exception_denylist=[DisconnectionError,TimeoutError],)registry.register(breaker)@wraps(func)defwrapper(*args:Any,**kwargs:Any)->Any:returnbreaker.call(func,*args,**kwargs)returncircuit_wrapper@db_breakerdefcall_to_db():...@db_breakerdefanother_call_to_db():...Reset StrategiesBy default, pycircuitbreaker operates such that a single success resets the error state of a closed breaker. This makes sense for a service that rarely fails, but in certains cases this can pose a problem. If theerror_thresholdis set to5, but only 4/5 external requests fail, the breaker will never open. To get around this, thestrategy settingmay be used. By setting this topycircuitbreaker.CircuitBreakerStrategy.NET_ERROR, the net error count (errors - successes) will be used to trigger the breaker.ConfigurationA number of configuration options can be provided to theCircuitBreakerclass or thecircuitdecorator to control the behaviour of the breaker. When using the decorator, options should be passed as keyword arguments.breaker_idThe ID of the breaker used in exception reporting or for logging purposes. If not specified, auuid4()is created.detect_errorType:Optional[Callable[Any, bool]]This option can be used to detect errors that do not raise exceptions. For example, if you have a function that returns a response object with a status code, we can detect errors that have a status code of 500.frompycircuitbreakerimportcircuitdefdetect_500(response)->bool:returnresponse.status_code==500@circuit(detect_error=detect_500)defrequest():response=external_call()returnresponseerror_thresholdType:Optional[int]Default:5The number of sequential errors that must occur before the breaker opens. If 4 errors occur a single success will reset the error count to 0.exception_denylistType:Optional[Iterable[Exception]]There are cases where only certain errors should count as errors that can open the breaker. In the example below, we are usingrequeststo call to an external service and then raise an exception on an error case. We only want the circuit breaker to open on timeouts to
the external service.Note that if this option is used, errors derived from those specified will also be included in the denylist.importrequestsfrompycircuitbreakerimportcircuit@circuit(exception_denylist=[requests.exceptions.Timeout])defexternal_call():response=requests.get("EXTERNAL_SERVICE")response.raise_for_status()exception_allowlistType:Optional[Iterable[Exception]]This setting allows certain exceptions to not be counted as errors. Taking the same example as the exception_denylist setting, we can ignorerequest.exceptions.HTTPErroronly using the allowlist.Note that if this option is used, errors derived from those specified will also be included in the allowlist.importrequestsfrompycircuitbreakerimportcircuit@circuit(exception_allowlist=[requests.exceptions.HTTPError])defexternal_call():response=requests.get("EXTERNAL_SERVICE")response.raise_for_status()on_closeType:Optional[Callable[[CircuitBreaker], None]]If specified, this function is called when the breaker fully closes. This can be useful for logging messages.on_openType:Optional[Callable[[CircuitBreaker, Union[Exception, Any]], None]]If specified, this function is called when the breaker opens. The 2nd parameter to the function will be the exception that triggered the opening if exception detection was used. If thedetect_errormethod was used, the wrapped function return value is passed as the 2nd parameter.recovery_thresholdType:Optional[int]Default:1This is the number of successful calls that must occur before the circuit breaker transitions fromCircuitBreakerState.HALF_OPENtoCircuitBreakerState.CLOSED.recovery_timeoutType:Optional[int]Default: 30The number of seconds the breaker stays fully open for before test requests are allowed through.strategyType:Optional[pycircuitbreaker.CircuitBreakerStrategy]Default:pycircuitbreaker.CircuitBreakerStrategy.SINGLE_RESETControls how successes change the error count when the breaker is closed. By default, a single success resets the number of errors in the breaker.Possible options:CircuitBreakerStrategy.SINGLE_RESETCircuitBreakerStrategy.NET_ERRORCircuitBreaker APIThe public API of theCircuitBreakerclass is described below.error_countType:intThe number of errors stored in the breaker.idThe ID of the breaker. If not supplied via the configurationbreaker_idsetting, this is auuid4().open_timeType:datetimeThe UTC time the breaker last opened.recovery_start_timeType:datetimeThe UTC time that the breaker is open until (when recovery begins).stateType:CircuitBreakerStateThe state of the breaker.success_countType:intThe number of successes stored in the breaker during the recovery period.RoadmapBack circuit breaker state with Redis to share state among processes (e.g. for gunicorn)