1CircuitBreaker 2-------------- 3 4.. image:: https://badge.fury.io/py/circuitbreaker.svg 5 :target: https://badge.fury.io/py/circuitbreaker 6 7.. image:: https://travis-ci.org/fabfuel/circuitbreaker.svg?branch=master 8 :target: https://travis-ci.org/fabfuel/circuitbreaker 9 10.. image:: https://scrutinizer-ci.com/g/fabfuel/circuitbreaker/badges/coverage.png?b=master 11 :target: https://scrutinizer-ci.com/g/fabfuel/circuitbreaker 12 13.. image:: https://scrutinizer-ci.com/g/fabfuel/circuitbreaker/badges/quality-score.png?b=master 14 :target: https://scrutinizer-ci.com/g/fabfuel/circuitbreaker 15 16This is a Python implementation of the "Circuit Breaker" Pattern (http://martinfowler.com/bliki/CircuitBreaker.html). 17Inspired by Michael T. Nygard's highly recommendable book *Release It!* (https://pragprog.com/book/mnee/release-it). 18 19 20Installation 21------------ 22 23The project is available on PyPI. Simply run:: 24 25 $ pip install circuitbreaker 26 27 28Usage 29----- 30 31This is the simplest example. Just decorate a function with the ``@circuit`` decorator:: 32 33 from circuitbreaker import circuit 34 35 @circuit 36 def external_call(): 37 ... 38 39 40This decorator sets up a circuit breaker with the default settings. The circuit breaker: 41 42- monitors the function execution and counts failures 43- resets the failure count after every successful execution (while it is closed) 44- opens and prevents further executions after 5 subsequent failures 45- switches to half-open and allows one test-execution after 30 seconds recovery timeout 46- closes if the test-execution succeeded 47- considers all raised exceptions (based on class ``Exception``) as an expected failure 48- is named "external_call" - the name of the function it decorates 49 50 51What does *failure* mean? 52========================= 53A *failure* is a raised exception, which was not caught during the function call. 54By default, the circuit breaker listens for all exceptions based on the class ``Exception``. 55That means, that all exceptions raised during the function call are considered as an 56"expected failure" and will increase the failure count. 57 58Get specific about the expected failure 59======================================= 60It is important, to be **as specific as possible**, when defining the expected exception. 61The main purpose of a circuit breaker is to protect your distributed system from a cascading failure. 62That means, you probably want to open the circuit breaker only, if the integration point on the other 63end is unavailable. So e.g. if there is an ``ConnectionError`` or a request ``Timeout``. 64 65If you are e.g. using the requests library (http://docs.python-requests.org/) for making HTTP calls, 66its ``RequestException`` class would be a great choice for the ``expected_exception`` parameter. 67 68All recognized exceptions will be re-raised anyway, but the goal is, to let the circuit breaker only 69recognize those exceptions which are related to the communication to your integration point. 70 71When it comes to monitoring (see Monitoring_), it may lead to falsy conclusions, if a 72circuit breaker opened, due to a local ``OSError`` or ``KeyError``, etc. 73 74 75Configuration 76------------- 77The following configuration options can be adjusted via decorator parameters. For example:: 78 79 from circuitbreaker import circuit 80 81 @circuit(failure_threshold=10, expected_exception=ConnectionError) 82 def external_call(): 83 ... 84 85 86 87failure threshold 88================= 89By default, the circuit breaker opens after 5 subsequent failures. You can adjust this value with the ``failure_threshold`` parameter. 90 91recovery timeout 92================ 93By default, the circuit breaker stays open for 30 seconds to allow the integration point to recover. 94You can adjust this value with the ``recovery_timeout`` parameter. 95 96expected exception 97================== 98By default, the circuit breaker listens for all exceptions which are based on the ``Exception`` class. 99You can adjust this with the ``expected_exception`` parameter. It can be either an exception class or a tuple of exception classes. 100 101name 102==== 103By default, the circuit breaker name is the name of the function it decorates. You can adjust the name with parameter ``name``. 104 105 106Advanced Usage 107-------------- 108If you apply circuit breakers to a couple of functions and you always set specific options other than the default values, 109you can extend the ``CircuitBreaker`` class and create your own circuit breaker subclass instead:: 110 111 from circuitbreaker import CircuitBreaker 112 113 class MyCircuitBreaker(CircuitBreaker): 114 FAILURE_THRESHOLD = 10 115 RECOVERY_TIMEOUT = 60 116 EXPECTED_EXCEPTION = RequestException 117 118 119Now you have two options to apply your circuit breaker to a function. As an Object directly:: 120 121 @MyCircuitBreaker() 122 def external_call(): 123 ... 124 125Please note, that the circuit breaker class has to be initialized, you have to use a class instance as decorator (``@MyCircuitBreaker()``), not the class itself (``@MyCircuitBreaker``). 126 127Or via the decorator proxy:: 128 129 @circuit(cls=MyCircuitBreaker) 130 def external_call(): 131 ... 132 133 134.. _Monitoring: 135 136Monitoring 137---------- 138To keep track of the health of your application and the state of your circuit breakers, every circuit breaker registers itself at the ``CircuitBreakerMonitor``. You can receive all registered circuit breakers via ``CircuitBreakerMonitor.get_circuits()``. 139 140To get an aggregated health status, you can ask the Monitor via ``CircuitBreakerMonitor.all_closed()``. Or you can retrieve the currently open circuits via ``CircuitBreakerMonitor.get_open()`` and the closed circuits via ``CircuitBreakerMonitor.get_closed()``. 141 142 143Todo 144---- 145- add unit tests 146