{"id":13814242,"url":"https://github.com/danielfm/pybreaker","last_synced_at":"2026-01-31T16:13:37.453Z","repository":{"id":1121326,"uuid":"993698","full_name":"danielfm/pybreaker","owner":"danielfm","description":"Python implementation of the Circuit Breaker pattern.","archived":false,"fork":false,"pushed_at":"2025-09-21T15:13:03.000Z","size":182,"stargazers_count":625,"open_issues_count":20,"forks_count":82,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-10-24T18:47:17.533Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielfm.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"danielfm","liberapay":"danielfm"}},"created_at":"2010-10-17T00:50:46.000Z","updated_at":"2025-10-23T19:06:40.000Z","dependencies_parsed_at":"2023-12-11T19:26:39.341Z","dependency_job_id":"68a515e9-79da-48c1-bb96-4d65bc81ef30","html_url":"https://github.com/danielfm/pybreaker","commit_stats":{"total_commits":118,"total_committers":25,"mean_commits":4.72,"dds":0.6610169491525424,"last_synced_commit":"7d49b05d4c1460af46b76b24badc01b59c7bb807"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/danielfm/pybreaker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielfm%2Fpybreaker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielfm%2Fpybreaker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielfm%2Fpybreaker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielfm%2Fpybreaker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielfm","download_url":"https://codeload.github.com/danielfm/pybreaker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielfm%2Fpybreaker/sbom","scorecard":{"id":319670,"data":{"date":"2025-08-11","repo":{"name":"github.com/danielfm/pybreaker","commit":"117908df34e97eef7954d5753a31dd0d17cd83f8"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":3,"reason":"3 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":6,"reason":"Found 18/30 approved changesets -- score normalized to 6","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql-analysis.yml:28","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql-analysis.yml:29","Warn: no topLevel permission defined: .github/workflows/build.yml:1","Warn: no topLevel permission defined: .github/workflows/codeql-analysis.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/danielfm/pybreaker/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/danielfm/pybreaker/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/danielfm/pybreaker/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/danielfm/pybreaker/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/danielfm/pybreaker/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:56: update your workflow using https://app.stepsecurity.io/secureworkflow/danielfm/pybreaker/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:70: update your workflow using https://app.stepsecurity.io/secureworkflow/danielfm/pybreaker/codeql-analysis.yml/main?enable=pin","Info:   0 out of   7 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":7,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 0 commits out of 19 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-18T01:04:52.917Z","repository_id":1121326,"created_at":"2025-08-18T01:04:52.918Z","updated_at":"2025-08-18T01:04:52.918Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28947572,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T14:26:55.697Z","status":"ssl_error","status_checked_at":"2026-01-31T14:26:52.545Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T04:01:48.491Z","updated_at":"2026-01-31T16:13:37.448Z","avatar_url":"https://github.com/danielfm.png","language":"Python","readme":"\nPyBreaker\n=========\n\nPyBreaker is a Python implementation of the Circuit Breaker pattern, described\nin Michael T. Nygard's book `Release It!`_.\n\nIn Nygard's words, *\"circuit breakers exists to allow one subsystem to fail\nwithout destroying the entire system. This is done by wrapping dangerous\noperations (typically integration points) with a component that can circumvent\ncalls when the system is not healthy\"*.\n\n\nFeatures\n--------\n\n* Configurable list of excluded exceptions (e.g. business exceptions)\n* Configurable failure threshold and reset timeout\n* Support for several event listeners per circuit breaker\n* Can guard generator functions\n* Functions and properties for easy monitoring and management\n* Thread-safe\n* Optional redis backing\n* Optional support for asynchronous Tornado calls\n\n\nRequirements\n------------\n\n* `Python`_ 3.10+\n\n\nInstallation\n------------\n\nRun the following command line to download the latest stable version of\nPyBreaker from `PyPI`_::\n\n    $ pip install pybreaker\n\nIf you are a `Git`_ user, you might want to install the current development\nversion in editable mode::\n\n    $ git clone git://github.com/danielfm/pybreaker.git\n    $ cd pybreaker\n    $ # run tests (on windows omit ./)\n    $ ./pw test\n    $ pip install -e .\n\n\nUsage\n-----\n\nThe first step is to create an instance of ``CircuitBreaker`` for each\nintegration point you want to protect against:\n\n.. code:: python\n\n    import pybreaker\n\n    # Used in database integration points\n    db_breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=60)\n\n\n``CircuitBreaker`` instances should live globally inside the application scope,\ne.g., live across requests.\n\nYou can also configure a success threshold to require multiple successful\nrequests before closing the circuit breaker:\n\n.. code:: python\n\n    # Require 3 successful requests before closing\n    db_breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=60, success_threshold=3)\n\n.. note::\n\n  Integration points to external services (i.e. databases, queues, etc) are\n  more likely to fail, so make sure to always use timeouts when accessing such\n  services if there's support at the API level.\n\nIf you'd like to use the Redis backing, initialize the ``CircuitBreaker`` with\na ``CircuitRedisStorage``:\n\n.. code:: python\n\n    import pybreaker\n    import redis\n\n    redis = redis.StrictRedis()\n    db_breaker = pybreaker.CircuitBreaker(\n        fail_max=5,\n        reset_timeout=60,\n        state_storage=pybreaker.CircuitRedisStorage(pybreaker.STATE_CLOSED, redis))\n\n**Do not** initialize the Redis connection with the ``decode_responses`` set to\n``True``, this will force returning ASCII objects from redis and in Python3+ will\nfail with:\n\n    `AttributeError: 'str' object has no attribute 'decode'`\n\n\n.. note::\n\n  You may want to reuse a connection already created in your application, if you're\n  using ``django_redis`` for example:\n\n.. code:: python\n\n    import pybreaker\n    from django_redis import get_redis_connection\n\n    db_breaker = pybreaker.CircuitBreaker(\n        fail_max=5,\n        reset_timeout=60,\n        state_storage=pybreaker.CircuitRedisStorage(pybreaker.STATE_CLOSED, get_redis_connection('default')))\n\n.. note::\n\n  If you require multiple, independent CircuitBreakers and wish to store their states in Redis, it is essential to assign a ``unique namespace`` for each\n  CircuitBreaker instance. This can be achieved by specifying a distinct namespace parameter in the CircuitRedisStorage constructor. for example:\n\n.. code:: python\n\n    import pybreaker\n    from django_redis import get_redis_connection\n\n    db_breaker = pybreaker.CircuitBreaker(\n        fail_max=5,\n        reset_timeout=60,\n        state_storage=pybreaker.CircuitRedisStorage(pybreaker.STATE_CLOSED, get_redis_connection('default'),namespace='unique_namespace'))\n\nEvent Listening\n```````````````\n\nThere's no need to subclass ``CircuitBreaker`` if you just want to take action\nwhen certain events occur. In that case, it's better to subclass\n``CircuitBreakerListener`` instead:\n\n.. code:: python\n\n    class DBListener(pybreaker.CircuitBreakerListener):\n        \"Listener used by circuit breakers that execute database operations.\"\n\n        def before_call(self, cb, func, *args, **kwargs):\n            \"Called before the circuit breaker `cb` calls `func`.\"\n            pass\n\n        def state_change(self, cb, old_state, new_state):\n            \"Called when the circuit breaker `cb` state changes.\"\n            pass\n\n        def failure(self, cb, exc):\n            \"Called when a function invocation raises a system error.\"\n            pass\n\n        def success(self, cb):\n            \"Called when a function invocation succeeds.\"\n            pass\n\n    class LogListener(pybreaker.CircuitBreakerListener):\n        \"Listener used to log circuit breaker events.\"\n\n        def state_change(self, cb, old_state, new_state):\n            msg = \"State Change: CB: {0}, New State: {1}\".format(cb.name, new_state)\n            logging.info(msg)\n\n\nTo add listeners to a circuit breaker:\n\n.. code:: python\n\n    # At creation time...\n    db_breaker = pybreaker.CircuitBreaker(listeners=[DBListener(), LogListener()])\n\n    # ...or later\n    db_breaker.add_listeners(OneListener(), AnotherListener())\n\n\nWhat Does a Circuit Breaker Do?\n```````````````````````````````\n\nLet's say you want to use a circuit breaker on a function that updates a row\nin the ``customer`` database table:\n\n.. code:: python\n\n    @db_breaker\n    def update_customer(cust):\n        # Do stuff here...\n        pass\n\n    # Will trigger the circuit breaker\n    updated_customer = update_customer(my_customer)\n\n\nOr if you don't want to use the decorator syntax:\n\n.. code:: python\n\n    def update_customer(cust):\n        # Do stuff here...\n        pass\n\n    # Will trigger the circuit breaker\n    updated_customer = db_breaker.call(update_customer, my_customer)\n\nOr use it as a context manager and a `with` statement:\n\n.. code:: python\n\n    # Will trigger the circuit breaker\n    with db_breaker.calling():\n        # Do stuff here...\n        pass\n\n\n\nAccording to the default parameters, the circuit breaker ``db_breaker`` will\nautomatically open the circuit after 5 consecutive failures in\n``update_customer``.\n\nWhen the circuit is open, all calls to ``update_customer`` will fail immediately\n(raising ``CircuitBreakerError``) without any attempt to execute the real\noperation. If you want the original error to be thrown when the circuit trips,\nset the ``throw_new_error_on_trip`` option to ``False``:\n\n.. code:: python\n\n    pybreaker.CircuitBreaker(..., throw_new_error_on_trip=False)\n\n\nAfter 60 seconds, the circuit breaker will allow the next call to\n``update_customer`` pass through. If that call succeeds, the circuit is closed;\nif it fails, however, the circuit is opened again until another timeout elapses.\n\nOptional Tornado Support\n````````````````````````\nA circuit breaker can (optionally) be used to call asynchronous Tornado functions:\n\n.. code:: python\n\n    from tornado import gen\n\n    @db_breaker(__pybreaker_call_async=True)\n    @gen.coroutine\n    def async_update(cust):\n        # Do async stuff here...\n        pass\n\nOr if you don't want to use the decorator syntax:\n\n.. code:: python\n\n    @gen.coroutine\n    def async_update(cust):\n        # Do async stuff here...\n        pass\n\n    updated_customer = db_breaker.call_async(async_update, my_customer)\n\n\nExcluding Exceptions\n````````````````````\n\nBy default, a failed call is any call that raises an exception. However, it's\ncommon to raise exceptions to also indicate business exceptions, and those\nexceptions should be ignored by the circuit breaker as they don't indicate\nsystem errors:\n\n.. code:: python\n\n    # At creation time...\n    db_breaker = CircuitBreaker(exclude=[CustomerValidationError])\n\n    # ...or later\n    db_breaker.add_excluded_exception(CustomerValidationError)\n\n\nIn that case, when any function guarded by that circuit breaker raises\n``CustomerValidationError`` (or any exception derived from\n``CustomerValidationError``), that call won't be considered a system failure.\n\nSo as to cover cases where the exception class alone is not enough to determine\nwhether it represents a system error, you may also pass a callable rather than\na type:\n\n.. code:: python\n\n    db_breaker = CircuitBreaker(exclude=[lambda e: type(e) == HTTPError and e.status_code \u003c 500])\n\nYou may mix types and filter callables freely.\n\n\nMonitoring and Management\n`````````````````````````\n\nA circuit breaker provides properties and functions you can use to monitor and\nchange its current state:\n\n.. code:: python\n\n    # Get the current number of consecutive failures\n    print(db_breaker.fail_counter)\n\n    # Get the current number of consecutive successes\n    print(db_breaker.success_counter)\n\n    # Get/set the maximum number of consecutive failures\n    print(db_breaker.fail_max)\n    db_breaker.fail_max = 10\n\n    # Get/set the success threshold\n    print(db_breaker.success_threshold)\n    db_breaker.success_threshold = 3\n\n    # Get/set the current reset timeout period (in seconds)\n    print db_breaker.reset_timeout\n    db_breaker.reset_timeout = 60\n\n    # Get the current state, i.e., 'open', 'half-open', 'closed'\n    print(db_breaker.current_state)\n\n    # Closes the circuit\n    db_breaker.close()\n\n    # Half-opens the circuit\n    db_breaker.half_open()\n\n    # Opens the circuit\n    db_breaker.open()\n\n\nThese properties and functions might and should be exposed to the operations\nstaff somehow as they help them to detect problems in the system.\n\nContributing\n-------------\n\nRun tests::\n\n    $ ./pw test\n\nCode formatting (black and isort) and linting (mypy) ::\n\n    $ ./pw format\n    $ ./pw lint\n\nAbove commands will automatically install the necessary tools inside *.pyprojectx*\nand also install pre-commit hooks.\n\nList available commands::\n\n    $ ./pw -i\n\n.. _Python: http://python.org\n.. _Jython: http://jython.org\n.. _Release It!: https://pragprog.com/titles/mnee2/release-it-second-edition/\n.. _PyPI: http://pypi.python.org\n.. _Git: http://git-scm.com\n","funding_links":["https://github.com/sponsors/danielfm","https://liberapay.com/danielfm"],"categories":["Topics Index","Python"],"sub_categories":["Commonly Useful Knowledge"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielfm%2Fpybreaker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielfm%2Fpybreaker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielfm%2Fpybreaker/lists"}