circuitbreaker


Namecircuitbreaker JSON
Version 2.0.0 PyPI version JSON
download
home_pagehttps://github.com/fabfuel/circuitbreaker
SummaryPython Circuit Breaker pattern implementation
upload_time2023-05-15 13:11:04
maintainer
docs_urlNone
authorFabian Fuelling
requires_python
licenseBSD-3-Clause
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            CircuitBreaker
--------------

.. image:: https://badge.fury.io/py/circuitbreaker.svg
    :target: https://badge.fury.io/py/circuitbreaker

.. image:: https://github.com/fabfuel/circuitbreaker/actions/workflows/build.yml/badge.svg
    :target: https://github.com/fabfuel/circuitbreaker/actions/workflows/build.yml

This is a Python implementation of the "Circuit Breaker" Pattern (https://martinfowler.com/bliki/CircuitBreaker.html).
Inspired by Michael T. Nygard's highly recommendable book *Release It!* (https://pragprog.com/titles/mnee2/release-it-second-edition/).


Installation
------------

The project is available on PyPI. Simply run::

    $ pip install circuitbreaker


Usage
-----

This is the simplest example. Just decorate a function with the ``@circuit`` decorator::

    from circuitbreaker import circuit

    @circuit
    def external_call():
        ...

Async functions are also supported::

    @circuit
    async def external_call():
        ...

This decorator sets up a circuit breaker with the default settings. The circuit breaker:

- monitors the function execution and counts failures
- resets the failure count after every successful execution (while it is closed)
- opens and prevents further executions after 5 subsequent failures
- switches to half-open and allows one test-execution after 30 seconds recovery timeout
- closes if the test-execution succeeded
- considers all raised exceptions (based on class ``Exception``) as an expected failure
- is named "external_call" - the name of the function it decorates


What does *failure* mean?
=========================
A *failure* is a raised exception, which was not caught during the function call.
By default, the circuit breaker listens for all exceptions based on the class ``Exception``.
That means, that all exceptions raised during the function call are considered as an
"expected failure" and will increase the failure count.

Get specific about the expected failure
=======================================
It is important, to be **as specific as possible**, when defining the expected exception.
The main purpose of a circuit breaker is to protect your distributed system from a cascading failure.
That means, you probably want to open the circuit breaker only, if the integration point on the other
end is unavailable. So e.g. if there is an ``ConnectionError`` or a request ``Timeout``.

If you are e.g. using the requests library (https://docs.python-requests.org/) for making HTTP calls,
its ``RequestException`` class would be a great choice for the ``expected_exception`` parameter.

The logic for treating thrown exceptions as failures can also be customized by passing a callable. The
callable will be passed the exception type and value, and should return True if the exception should be
treated as a failure.

All recognized exceptions will be re-raised anyway, but the goal is, to let the circuit breaker only
recognize those exceptions which are related to the communication to your integration point.

When it comes to monitoring (see Monitoring_), it may lead to falsy conclusions, if a
circuit breaker opened, due to a local ``OSError`` or ``KeyError``, etc.


Configuration
-------------
The following configuration options can be adjusted via decorator parameters. For example::

    from circuitbreaker import circuit

    @circuit(failure_threshold=10, expected_exception=ConnectionError)
    def external_call():
        ...



failure threshold
=================
By default, the circuit breaker opens after 5 subsequent failures. You can adjust this value with the ``failure_threshold`` parameter.

recovery timeout
================
By default, the circuit breaker stays open for 30 seconds to allow the integration point to recover.
You can adjust this value with the ``recovery_timeout`` parameter.

expected exception
==================
By default, the circuit breaker listens for all exceptions which are based on the ``Exception`` class.
You can adjust this with the ``expected_exception`` parameter. It can be either an exception class, an iterable of an exception classes,
or a callable.

Use a callable if the logic to flag exceptions as failures is more complex than a type check. For example::

    # Assume we are using the requests library
    def is_not_http_error(thrown_type, thrown_value):
        return issubclass(thrown_type, RequestException) and not issubclass(thrown_type, HTTPError)

    def is_rate_limited(thrown_type, thrown_value):
        return issubclass(thrown_type, HTTPError) and thrown_value.status_code == 429

    @circuit(expected_exception=is_not_http_error)
    def call_flaky_api(...):
        rsp = requests.get(...)
        rsp.raise_for_status()
        return rsp

    @circuit(expected_exception=is_rate_limited)
    def call_slow_server(...):
        rsp = requests.get(...)
        rsp.raise_for_status()
        return rsp
        ```

name
====
By default, the circuit breaker name is the name of the function it decorates. You can adjust the name with parameter ``name``.

fallback function
=================
By default, the circuit breaker will raise a ``CircuitBreaker`` exception when the circuit is opened.
You can instead specify a function to be called when the circuit is opened. This function can be specified with the
``fallback_function`` parameter and will be called with the same parameters as the decorated function would be.

The fallback type of call must also match the decorated function. For instance, if the decorated function is an
async generator, the ``fallback_function`` must be an async generator as well.

Advanced Usage
--------------
If you apply circuit breakers to a couple of functions and you always set specific options other than the default values,
you can extend the ``CircuitBreaker`` class and create your own circuit breaker subclass instead::

    from circuitbreaker import CircuitBreaker

    class MyCircuitBreaker(CircuitBreaker):
        FAILURE_THRESHOLD = 10
        RECOVERY_TIMEOUT = 60
        EXPECTED_EXCEPTION = RequestException


Now you have two options to apply your circuit breaker to a function. As an Object directly::

    @MyCircuitBreaker()
    def external_call():
        ...

Please 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``).

Or via the decorator proxy::

    @circuit(cls=MyCircuitBreaker)
    def external_call():
        ...


.. _Monitoring:

Monitoring
----------
To 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()``.

To 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()``.



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/fabfuel/circuitbreaker",
    "name": "circuitbreaker",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "",
    "author": "Fabian Fuelling",
    "author_email": "pypi@fabfuel.de",
    "download_url": "https://files.pythonhosted.org/packages/23/57/3bc8f0885c6914336d0b2fe36bf740476f0c827b3fb991993d67c1a9d3f3/circuitbreaker-2.0.0.tar.gz",
    "platform": "any",
    "description": "CircuitBreaker\n--------------\n\n.. image:: https://badge.fury.io/py/circuitbreaker.svg\n    :target: https://badge.fury.io/py/circuitbreaker\n\n.. image:: https://github.com/fabfuel/circuitbreaker/actions/workflows/build.yml/badge.svg\n    :target: https://github.com/fabfuel/circuitbreaker/actions/workflows/build.yml\n\nThis is a Python implementation of the \"Circuit Breaker\" Pattern (https://martinfowler.com/bliki/CircuitBreaker.html).\nInspired by Michael T. Nygard's highly recommendable book *Release It!* (https://pragprog.com/titles/mnee2/release-it-second-edition/).\n\n\nInstallation\n------------\n\nThe project is available on PyPI. Simply run::\n\n    $ pip install circuitbreaker\n\n\nUsage\n-----\n\nThis is the simplest example. Just decorate a function with the ``@circuit`` decorator::\n\n    from circuitbreaker import circuit\n\n    @circuit\n    def external_call():\n        ...\n\nAsync functions are also supported::\n\n    @circuit\n    async def external_call():\n        ...\n\nThis decorator sets up a circuit breaker with the default settings. The circuit breaker:\n\n- monitors the function execution and counts failures\n- resets the failure count after every successful execution (while it is closed)\n- opens and prevents further executions after 5 subsequent failures\n- switches to half-open and allows one test-execution after 30 seconds recovery timeout\n- closes if the test-execution succeeded\n- considers all raised exceptions (based on class ``Exception``) as an expected failure\n- is named \"external_call\" - the name of the function it decorates\n\n\nWhat does *failure* mean?\n=========================\nA *failure* is a raised exception, which was not caught during the function call.\nBy default, the circuit breaker listens for all exceptions based on the class ``Exception``.\nThat means, that all exceptions raised during the function call are considered as an\n\"expected failure\" and will increase the failure count.\n\nGet specific about the expected failure\n=======================================\nIt is important, to be **as specific as possible**, when defining the expected exception.\nThe main purpose of a circuit breaker is to protect your distributed system from a cascading failure.\nThat means, you probably want to open the circuit breaker only, if the integration point on the other\nend is unavailable. So e.g. if there is an ``ConnectionError`` or a request ``Timeout``.\n\nIf you are e.g. using the requests library (https://docs.python-requests.org/) for making HTTP calls,\nits ``RequestException`` class would be a great choice for the ``expected_exception`` parameter.\n\nThe logic for treating thrown exceptions as failures can also be customized by passing a callable. The\ncallable will be passed the exception type and value, and should return True if the exception should be\ntreated as a failure.\n\nAll recognized exceptions will be re-raised anyway, but the goal is, to let the circuit breaker only\nrecognize those exceptions which are related to the communication to your integration point.\n\nWhen it comes to monitoring (see Monitoring_), it may lead to falsy conclusions, if a\ncircuit breaker opened, due to a local ``OSError`` or ``KeyError``, etc.\n\n\nConfiguration\n-------------\nThe following configuration options can be adjusted via decorator parameters. For example::\n\n    from circuitbreaker import circuit\n\n    @circuit(failure_threshold=10, expected_exception=ConnectionError)\n    def external_call():\n        ...\n\n\n\nfailure threshold\n=================\nBy default, the circuit breaker opens after 5 subsequent failures. You can adjust this value with the ``failure_threshold`` parameter.\n\nrecovery timeout\n================\nBy default, the circuit breaker stays open for 30 seconds to allow the integration point to recover.\nYou can adjust this value with the ``recovery_timeout`` parameter.\n\nexpected exception\n==================\nBy default, the circuit breaker listens for all exceptions which are based on the ``Exception`` class.\nYou can adjust this with the ``expected_exception`` parameter. It can be either an exception class, an iterable of an exception classes,\nor a callable.\n\nUse a callable if the logic to flag exceptions as failures is more complex than a type check. For example::\n\n    # Assume we are using the requests library\n    def is_not_http_error(thrown_type, thrown_value):\n        return issubclass(thrown_type, RequestException) and not issubclass(thrown_type, HTTPError)\n\n    def is_rate_limited(thrown_type, thrown_value):\n        return issubclass(thrown_type, HTTPError) and thrown_value.status_code == 429\n\n    @circuit(expected_exception=is_not_http_error)\n    def call_flaky_api(...):\n        rsp = requests.get(...)\n        rsp.raise_for_status()\n        return rsp\n\n    @circuit(expected_exception=is_rate_limited)\n    def call_slow_server(...):\n        rsp = requests.get(...)\n        rsp.raise_for_status()\n        return rsp\n        ```\n\nname\n====\nBy default, the circuit breaker name is the name of the function it decorates. You can adjust the name with parameter ``name``.\n\nfallback function\n=================\nBy default, the circuit breaker will raise a ``CircuitBreaker`` exception when the circuit is opened.\nYou can instead specify a function to be called when the circuit is opened. This function can be specified with the\n``fallback_function`` parameter and will be called with the same parameters as the decorated function would be.\n\nThe fallback type of call must also match the decorated function. For instance, if the decorated function is an\nasync generator, the ``fallback_function`` must be an async generator as well.\n\nAdvanced Usage\n--------------\nIf you apply circuit breakers to a couple of functions and you always set specific options other than the default values,\nyou can extend the ``CircuitBreaker`` class and create your own circuit breaker subclass instead::\n\n    from circuitbreaker import CircuitBreaker\n\n    class MyCircuitBreaker(CircuitBreaker):\n        FAILURE_THRESHOLD = 10\n        RECOVERY_TIMEOUT = 60\n        EXPECTED_EXCEPTION = RequestException\n\n\nNow you have two options to apply your circuit breaker to a function. As an Object directly::\n\n    @MyCircuitBreaker()\n    def external_call():\n        ...\n\nPlease 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``).\n\nOr via the decorator proxy::\n\n    @circuit(cls=MyCircuitBreaker)\n    def external_call():\n        ...\n\n\n.. _Monitoring:\n\nMonitoring\n----------\nTo 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()``.\n\nTo 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()``.\n\n\n",
    "bugtrack_url": null,
    "license": "BSD-3-Clause",
    "summary": "Python Circuit Breaker pattern implementation",
    "version": "2.0.0",
    "project_urls": {
        "Download": "https://github.com/fabfuel/circuitbreaker/archive/2.0.0.tar.gz",
        "Homepage": "https://github.com/fabfuel/circuitbreaker"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ace033bbe68911a05fdec50d89dcffeac7bf84050e101e2d6dd8088ac8a62cae",
                "md5": "90f3409f7d08d732c789f0813bd46262",
                "sha256": "c8c6f044b616cd5066368734ce4488020392c962b4bd2869d406d883c36d9859"
            },
            "downloads": -1,
            "filename": "circuitbreaker-2.0.0-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "90f3409f7d08d732c789f0813bd46262",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": null,
            "size": 7644,
            "upload_time": "2023-05-15T13:11:22",
            "upload_time_iso_8601": "2023-05-15T13:11:22.763697Z",
            "url": "https://files.pythonhosted.org/packages/ac/e0/33bbe68911a05fdec50d89dcffeac7bf84050e101e2d6dd8088ac8a62cae/circuitbreaker-2.0.0-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "23573bc8f0885c6914336d0b2fe36bf740476f0c827b3fb991993d67c1a9d3f3",
                "md5": "0fc04e58267a46ac371aac0f3e2b0105",
                "sha256": "28110761ca81a2accbd6b33186bc8c433e69b0933d85e89f280028dbb8c1dd14"
            },
            "downloads": -1,
            "filename": "circuitbreaker-2.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "0fc04e58267a46ac371aac0f3e2b0105",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 10736,
            "upload_time": "2023-05-15T13:11:04",
            "upload_time_iso_8601": "2023-05-15T13:11:04.820210Z",
            "url": "https://files.pythonhosted.org/packages/23/57/3bc8f0885c6914336d0b2fe36bf740476f0c827b3fb991993d67c1a9d3f3/circuitbreaker-2.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-05-15 13:11:04",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "fabfuel",
    "github_project": "circuitbreaker",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "circuitbreaker"
}
        
Elapsed time: 0.14351s