mrkeyoor.com_
Tue 22 Sept 00:45 UTC
PyPIUtilsupdated 20 Sept 2026

pybreaker review

PyBreaker 1.4.1 counts failures around a callable and opens a circuit after the configured threshold, causing later calls to fail immediately with `CircuitBreakerError`. After `reset_timeout`, one half-open trial decides whether recovery has started; `success_threshold` can require several good trials before closing. Decorators, direct calls, and a context manager all feed the same state machine. Listeners report transitions, exclusion rules keep business errors out of the count, and Redis storage can share state between processes. The async API is specifically for Tornado generator coroutines, so ordinary `async def` code is a poor match.

Verdict

PyBreaker 1.4.1 installed in 0.5 seconds as 1 MB and imported in 0.14 seconds in our sandbox, making it a cheap circuit state machine for synchronous Python services. Install it for shared Redis-backed tripping or listener hooks; use an await-native breaker for asyncio and keep real I/O timeouts either way.

We installed it

Lab card: what happened when we installed pybreakerScreenshot of pybreaker documentation
Install✓ · 0.5s1 package on disk · 1 MB
Importimport pybreaker in 0.14s · pure Python · py.typed · requires Python >=3.9
Known vulns0(pip-audit)

Answers from our run

Does pybreaker install cleanly?

Yes. In a fresh container with an empty cache, pip install pybreaker finished in 0.5s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does pybreaker need to run?

Python >=3.9, and nothing compiled: it is pure Python. In our run import pybreaker succeeded in 0.14s, and the package ships py.typed for type checkers.

pybreaker or circuitbreaker: which should you use?

circuitbreaker: Choose it for simple decorator-based circuits that do not need Redis coordination. PyBreaker 1.4.1 installed in 0.5 seconds as 1 MB and imported in 0.14 seconds in our sandbox, making it a cheap circuit state machine for synchronous Python services.

When should you not use pybreaker?

Protected calls are native async def. PyBreaker's asynchronous methods target Tornado generators and do not observe awaited asyncio failures.

API stability4/5Version 1.x still revolves around one `CircuitBreaker`, its decorator, `call`, the `calling` context, listener callbacks, excluded errors, and replaceable state storage. Constructor additions such as `success_threshold` and `throw_new_error_on_trip` preserve ordinary use. Release 1.4.1 dates to September 2025, providing a settled sync API without suggesting native asyncio is coming.
Docs3/5The README walks through open, half-open, and closed behavior, application-scoped instances, underlying timeouts, excluded exceptions, listeners, Redis, manual controls, and Tornado calls. Its warnings name the `decode_responses=True` crash and namespace collision. Readers get no separate signature reference, threshold-tuning advice, or direct statement explaining that native asyncio is unsupported.
Maintenance3/5GitHub shows a July 4, 2026 push, 692 stars, 24 open issues and pull requests, and an unarchived repository. That source activity is newer than PyPI release 1.4.1 from September 2025. Before relying on a recently closed bug or compatibility change, check whether the fix reached a package because repository movement has not yet produced another release.
Ecosystem3/5The stored current snapshot counts 8,882,184 weekly downloads. Any synchronous framework can use the decorator or direct-call wrapper, and Redis supplies the only distributed state backend documented in core. Tornado has a special coroutine path. Framework middleware, Prometheus collectors, OpenTelemetry spans, and native asyncio behavior must be added by the application or another dependency.

Use it if

  • A failing database, queue, or HTTP upstream keeps consuming workers with repeated doomed calls.
  • One integration should share a failure threshold and recovery window across all its call sites.
  • Multiple processes need the same circuit state and can rely on an existing Redis service.
  • Operations will export transition callbacks plus the built-in failure and success counters.
Skip it if

Setup reality

We installed pybreaker 1.4.1 in a clean Python 3.12 Bookworm sandbox. pip took 0.5 seconds, leaving 1 package and 1 MB on disk. The pure-Python distribution lists 7 direct dependencies, includes py.typed, and declares Python >=3.9. import pybreaker completed in 0.14 seconds, while pip-audit found 0 known vulnerabilities. Its metadata says BSD License. The README now states Python 3.10+, so 3.10 is the safer operational floor despite the looser package marker.

Keep one breaker alive for each dependency across requests. Constructing it inside a handler resets the failure counter every time and defeats the state machine. The protected network or database operation still needs its own timeout because a breaker cannot stop a call already blocked in I/O. Tune fail_max against actual failure bursts and exclude validation errors or expected 4xx responses that do not indicate an unhealthy upstream.

Default state is thread-safe and confined to one process. CircuitRedisStorage lets workers coordinate, but the README forbids decode_responses=True; enabling it produces AttributeError: 'str' object has no attribute 'decode'. Give every circuit a distinct Redis namespace or unrelated upstreams can share counters. Redis then sits in the protection path and needs separate monitoring.

Listener callbacks execute inline with the guarded call. Metrics and logging handlers must be quick and must not raise their own errors. The advertised asynchronous support covers Tornado's generator-coroutine convention through call_async and a decorator flag. It does not await a native coroutine's eventual exception, so asyncio services should use an await-aware breaker.

Patterns

Keep circuit state across requests create-breaker

import pybreaker

payments_breaker = pybreaker.CircuitBreaker(
    name='payments-api',
    fail_max=5,
    reset_timeout=60,
)

Module scope preserves counters. Constructing this object inside each request erases the history needed to open.

Decorate a database operation decorate-function

@payments_breaker
def charge(order_id, amount):
    return payments_client.charge(order_id, amount, timeout=3)

The database client needs its own timeout; circuit state cannot interrupt I/O already underway.

Guard code you cannot decorate call-explicitly

receipt = payments_breaker.call(
    payments_client.charge,
    order_id,
    amount,
)

`call` sends the callable and arguments through the same accounting without modifying third-party code.

Protect one integration block guard-block

with payments_breaker.calling():
    response = client.post('/charges', json=payload, timeout=3)
    response.raise_for_status()

Every non-excluded exception inside this context counts. Do not mix unrelated business logic into the block.

Serve cached data from an open circuit fallback-open-circuit

try:
    quote = pricing_breaker.call(fetch_quote, sku)
except pybreaker.CircuitBreakerError:
    quote = cached_quote(sku)

An open circuit skips the upstream entirely. The fallback must define acceptable stale and absent-data behavior.

Exclude validation failures exclude-business-error

orders_breaker = pybreaker.CircuitBreaker(
    fail_max=5,
    exclude=[InvalidCoupon, DuplicateOrder],
)

Subclass instances are excluded as well. Count only exceptions that indicate the protected service is unhealthy.

Ignore HTTP client mistakes exclude-http-client-errors

api_breaker = pybreaker.CircuitBreaker(
    exclude=[
        lambda error: isinstance(error, HTTPError)
        and error.response.status_code < 500
    ],
)

The predicate sees each exception object, allowing expected 4xx responses to bypass the health counter.

Demand three healthy trial calls require-recovery

api_breaker = pybreaker.CircuitBreaker(
    fail_max=5,
    reset_timeout=60,
    success_threshold=3,
)

The half-open state remains until 3 accepted trials succeed; an earlier failure opens it again.

Share one circuit through Redis share-redis-state

import redis

client = redis.StrictRedis.from_url('redis://redis:6379/0')
storage = pybreaker.CircuitRedisStorage(
    pybreaker.STATE_CLOSED, client, namespace='payments-api'
)
payments_breaker = pybreaker.CircuitBreaker(state_storage=storage)

A decoded-response Redis client triggers the documented string `.decode` crash. The namespace must also be unique per upstream.

Emit transition telemetry listen-state-change

class MetricsListener(pybreaker.CircuitBreakerListener):
    def state_change(self, breaker, old, new):
        breaker_state.labels(breaker.name).set(new.name == 'closed')

payments_breaker.add_listeners(MetricsListener())

This callback delays the protected call, so it should queue quick telemetry and swallow exporter failures.

Report counters and current state inspect-state

def breaker_status(breaker):
    return {
        'name': breaker.name,
        'state': breaker.current_state,
        'failures': breaker.fail_counter,
        'successes': breaker.success_counter,
    }

An open value describes the dependency guard, not a dead application. Keep it out of the process liveness decision.

Trip the breaker manually force-open

payments_breaker.open()
# restore automatic probing when the upstream is ready
payments_breaker.half_open()

With memory storage this change applies to one process. Redis storage is required for workers to observe the same state.

Alternatives

PackageRegistryPick it when
circuitbreakerPyPIChoose it for simple decorator-based circuits that do not need Redis coordination.
aiobreakerPyPIChoose it when failures occur after awaiting native asyncio functions.
tenacityPyPIChoose it for transient retries with separately configured wait, stop, and error policies.
staminaPyPIChoose it for concise synchronous or asynchronous retries with preset backoff and jitter.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.