Skip to main content

PyBreaker is a Python implementation of the Circuit Breaker pattern, described in Michael T. Nygard’s book Release It!.

In Nygard’s words, “circuit breakers exists to allow one subsystem to fail without destroying the entire system. This is done by wrapping dangerous operations (typically integration points) with a component that can circumvent calls when the system is not healthy”.

Features

  • Configurable list of excluded exceptions (e.g. business exceptions)

  • Configurable failure threshold and reset timeout

  • Support for several event listeners per circuit breaker

  • Can guard generator functions

  • Functions and properties for easy monitoring and management

  • Thread-safe

  • Optional redis backing

  • Optional support for asynchronous Tornado calls

Requirements

Installation

Run the following command line to download the latest stable version of PyBreaker from PyPI:

$ pip install pybreaker

If you are a Git user, you might want to install the current development version in editable mode:

$ git clone git://github.com/danielfm/pybreaker.git
$ cd pybreaker
$ # run tests (on windows omit ./)
$ ./pw test
$ pip install -e .

Usage

The first step is to create an instance of CircuitBreaker for each integration point you want to protect against:

import pybreaker

# Used in database integration points
db_breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=60)

CircuitBreaker instances should live globally inside the application scope, e.g., live across requests.

You can also configure a success threshold to require multiple successful requests before closing the circuit breaker:

# Require 3 successful requests before closing
db_breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=60, success_threshold=3)

If you’d like to use the Redis backing, initialize the CircuitBreaker with a CircuitRedisStorage:

import pybreaker
import redis

redis = redis.StrictRedis()
db_breaker = pybreaker.CircuitBreaker(
    fail_max=5,
    reset_timeout=60,
    state_storage=pybreaker.CircuitRedisStorage(pybreaker.STATE_CLOSED, redis))

Do not initialize the Redis connection with the decode_responses set to True, this will force returning ASCII objects from redis and in Python3+ will fail with:

AttributeError: ‘str’ object has no attribute ‘decode’

import pybreaker
from django_redis import get_redis_connection

db_breaker = pybreaker.CircuitBreaker(
    fail_max=5,
    reset_timeout=60,
    state_storage=pybreaker.CircuitRedisStorage(pybreaker.STATE_CLOSED, get_redis_connection('default')))
import pybreaker
from django_redis import get_redis_connection

db_breaker = pybreaker.CircuitBreaker(
    fail_max=5,
    reset_timeout=60,
    state_storage=pybreaker.CircuitRedisStorage(pybreaker.STATE_CLOSED, get_redis_connection('default'),namespace='unique_namespace'))

Event Listening

There’s no need to subclass CircuitBreaker if you just want to take action when certain events occur. In that case, it’s better to subclass CircuitBreakerListener instead:

class DBListener(pybreaker.CircuitBreakerListener):
    "Listener used by circuit breakers that execute database operations."

    def before_call(self, cb, func, *args, **kwargs):
        "Called before the circuit breaker `cb` calls `func`."
        pass

    def state_change(self, cb, old_state, new_state):
        "Called when the circuit breaker `cb` state changes."
        pass

    def failure(self, cb, exc):
        "Called when a function invocation raises a system error."
        pass

    def success(self, cb):
        "Called when a function invocation succeeds."
        pass

class LogListener(pybreaker.CircuitBreakerListener):
    "Listener used to log circuit breaker events."

    def state_change(self, cb, old_state, new_state):
        msg = "State Change: CB: {0}, New State: {1}".format(cb.name, new_state)
        logging.info(msg)

To add listeners to a circuit breaker:

# At creation time...
db_breaker = pybreaker.CircuitBreaker(listeners=[DBListener(), LogListener()])

# ...or later
db_breaker.add_listeners(OneListener(), AnotherListener())

What Does a Circuit Breaker Do?

Let’s say you want to use a circuit breaker on a function that updates a row in the customer database table:

@db_breaker
def update_customer(cust):
    # Do stuff here...
    pass

# Will trigger the circuit breaker
updated_customer = update_customer(my_customer)

Or if you don’t want to use the decorator syntax:

def update_customer(cust):
    # Do stuff here...
    pass

# Will trigger the circuit breaker
updated_customer = db_breaker.call(update_customer, my_customer)

Or use it as a context manager and a with statement:

# Will trigger the circuit breaker
with db_breaker.calling():
    # Do stuff here...
    pass

According to the default parameters, the circuit breaker db_breaker will automatically open the circuit after 5 consecutive failures in update_customer.

When the circuit is open, all calls to update_customer will fail immediately (raising CircuitBreakerError) without any attempt to execute the real operation. If you want the original error to be thrown when the circuit trips, set the throw_new_error_on_trip option to False:

pybreaker.CircuitBreaker(..., throw_new_error_on_trip=False)

After 60 seconds, the circuit breaker will allow the next call to update_customer pass through. If that call succeeds, the circuit is closed; if it fails, however, the circuit is opened again until another timeout elapses.

Optional Tornado Support

A circuit breaker can (optionally) be used to call asynchronous Tornado functions:

from tornado import gen

@db_breaker(__pybreaker_call_async=True)
@gen.coroutine
def async_update(cust):
    # Do async stuff here...
    pass

Or if you don’t want to use the decorator syntax:

@gen.coroutine
def async_update(cust):
    # Do async stuff here...
    pass

updated_customer = db_breaker.call_async(async_update, my_customer)

Excluding Exceptions

By default, a failed call is any call that raises an exception. However, it’s common to raise exceptions to also indicate business exceptions, and those exceptions should be ignored by the circuit breaker as they don’t indicate system errors:

# At creation time...
db_breaker = CircuitBreaker(exclude=[CustomerValidationError])

# ...or later
db_breaker.add_excluded_exception(CustomerValidationError)

In that case, when any function guarded by that circuit breaker raises CustomerValidationError (or any exception derived from CustomerValidationError), that call won’t be considered a system failure.

So as to cover cases where the exception class alone is not enough to determine whether it represents a system error, you may also pass a callable rather than a type:

db_breaker = CircuitBreaker(exclude=[lambda e: type(e) == HTTPError and e.status_code < 500])

You may mix types and filter callables freely.

Monitoring and Management

A circuit breaker provides properties and functions you can use to monitor and change its current state:

# Get the current number of consecutive failures
print(db_breaker.fail_counter)

# Get the current number of consecutive successes
print(db_breaker.success_counter)

# Get/set the maximum number of consecutive failures
print(db_breaker.fail_max)
db_breaker.fail_max = 10

# Get/set the success threshold
print(db_breaker.success_threshold)
db_breaker.success_threshold = 3

# Get/set the current reset timeout period (in seconds)
print db_breaker.reset_timeout
db_breaker.reset_timeout = 60

# Get the current state, i.e., 'open', 'half-open', 'closed'
print(db_breaker.current_state)

# Closes the circuit
db_breaker.close()

# Half-opens the circuit
db_breaker.half_open()

# Opens the circuit
db_breaker.open()

These properties and functions might and should be exposed to the operations staff somehow as they help them to detect problems in the system.

Contributing

Run tests:

$ ./pw test

Code formatting (black and isort) and linting (mypy)

$ ./pw format
$ ./pw lint

Above commands will automatically install the necessary tools inside .pyprojectx and also install pre-commit hooks.

List available commands:

$ ./pw -i

Release files for pybreaker 1.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pybreaker 1.4.1
File Size Uploaded
pybreaker-1.4.1.tar.gz 15.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pybreaker 1.4.1
File Interpreter ABI Platform
pybreaker-1.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 28.5 kB

Release files / pybreaker-1.4.1.tar.gz

Download URL pybreaker-1.4.1.tar.gz
Size 15.6 kB
Tags Source
SHA-256 checksum
How to use checksums
8df2d245c73ba40c8242c56ffb4f12138fbadc23e296224740c2028ea9dc1178
BLAKE2b-256 checksum
How to use checksums
f289fbf98e383f1ec6d117af2cd983efdb3eb7018b63834c427025764194cac2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2025.

Transparency log

Release files / pybreaker-1.4.1-py3-none-any.whl

Download URL pybreaker-1.4.1-py3-none-any.whl
Size 12.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b4dab4a05195b7f2a64a6c1a6c4ba7a96534ef56ea7210e6bcb59f28897160e0
BLAKE2b-256 checksum
How to use checksums
4475e64d3d40a741e2be21d69154f4e5c43a66f0c603c5ef11f49e01429a5932
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

1.4.1 This release

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.8.0

1 release file

0.7.0

1 release file

0.6.0

1 release file

0.5.0

1 release file

0.4.5

1 release file

0.4.4

1 release file

0.4.2

1 release file

0.4.1

1 release file

0.4.0

1 release file

0.3.3

1 release file

0.3.2

1 release file

0.3.1

1 release file

0.3.0

1 release file

0.2.3

1 release file

0.2.2

4 release files

0.2.1

1 release file

0.2

1 release file

0.1.1

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page