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