Skip to main content

interlock

CI Coverage OpenSSF Scorecard OpenSSF Best Practices PyPI Downloads Python versions License: MIT llms.txt Documentation Context7 CodSpeed

A modern circuit breaker for Python — sync and async in a single class, sliding-window rate and slow-call detection, a type-safe API, and transparent integrations at the transport level.

Why interlock

  • Sync and async, one class. A single CircuitBreaker detects coroutine callables and dispatches to the right path — no Sync*/Async* twins.
  • Sliding windows by rate. Both count-based and time-based windows, not the naive consecutive-failure counter found elsewhere in the ecosystem.
  • Slow-call detection. Treat calls slower than a threshold as failures — not available in any other Python circuit breaker.
  • Type-safe. ParamSpec + TypeVar decorators preserve the wrapped signature and its sync/async nature; ships py.typed, passes mypy, pyright and pyrefly in strict mode.
  • Zero-dependency core. Standard library only; everything external lives in optional extras (httpx2, aiohttp, requests, tenacity, fastapi, litestar, redis, otel).
  • Composable pipeline (v2). Timeout, bulkhead, breaker, retry and fallback as strategies applied in an explicit order — Polly-style, with the standalone breaker untouched.

How it compares

interlock-cb is young (first released in 2026). pybreaker and circuitbreaker are mature, well-documented and proven in production for years — for many projects they are exactly the right choice. Each library is strong in different places:

Feature interlock-cb pybreaker circuitbreaker
Core states (closed / open / half-open)
Choose which exceptions count as failures
Zero-dependency core
async / await (asyncio) Tornado
Event / state-change listeners
Shared state across processes (Redis)
Fallback function
Composable resilience pipeline
Years of production use new
Failure-rate sliding window
Time-based window
Slow-call detection
Result-based failure classification
Type-safe decorator (preserves signature)
Built-in httpx transport
OpenTelemetry metrics

Compared against pybreaker 1.x and circuitbreaker 2.1 as documented in mid-2026. pybreaker's async support is Tornado-based, not asyncio. Both established libraries trip on a consecutive-failure count rather than a rate window. Something out of date? Please open a PR.

Reach for an established library if you want a small, proven breaker today or a built-in fallback. Choose interlock-cb when you want rate-based windows, slow-call detection, coordinated state with graceful degradation and a fully typed API. The full comparison also covers aiobreaker and purgatory.

What interlock-cb offers in place of years in production: 100% branch coverage, three strict type checkers, mutation testing on the state machine and engine, property- and model-based tests, and CI on free-threaded CPython — see Correctness and testing for what is verified, how, and where the limits are.

Installation

uv add interlock-cb          # or: pip install interlock-cb

Optional extras:

uv add 'interlock-cb[fastapi]'  # FastAPI dependency + 503 Retry-After handler
uv add 'interlock-cb[litestar]' # Litestar dependency + 503 Retry-After handler
uv add 'interlock-cb[httpx2]'   # per-host httpx2 transport
uv add 'interlock-cb[aiohttp]'  # per-host aiohttp client middleware
uv add 'interlock-cb[requests]' # per-host requests session adapter
uv add 'interlock-cb[tenacity]' # retry × breaker composition helpers
uv add 'interlock-cb[redis]'    # shared breaker state across processes
uv add 'interlock-cb[otel]'     # OpenTelemetry metrics listener

Quickstart

Protect a callable three ways over the one call() primitive.

from interlock import CircuitBreaker, Config

breaker = CircuitBreaker(
    name='payments',
    config=Config(failure_rate_threshold=0.5, minimum_number_of_calls=20),
)


# 1. Decorator — preserves the signature and sync/async nature.
@breaker
def charge(amount: int) -> str:
    return gateway.charge(amount)


# 2. breaker.call — the breaker runs the callable.
result = breaker.call(gateway.charge, 100)

# 3. Context manager — guards a block (exceptions + duration only).
with breaker:
    gateway.charge(100)

The same instance works for async — the decorator and call detect a coroutine function, and the instance is also an async context manager:

@breaker
async def fetch(url: str) -> bytes:
    return await client.get(url)


async with breaker:
    await client.get(url)

When the circuit is open, the call is rejected with CircuitOpenError, which carries the breaker name, an estimate of when the next probe is allowed, and the last recorded failure:

from interlock import CircuitOpenError

try:
    breaker.call(gateway.charge, 100)
except CircuitOpenError as exc:
    print(exc.breaker_name, exc.retry_after, exc.last_failure)

Want to watch a breaker trip and recover? Run the examples — deterministic output, no network, every transition narrated (walkthrough).

Resilience pipeline

When one concern is not enough, compose strategies around a call in an explicit order (first = outermost) — the breaker stays a first-class standalone primitive:

from interlock import CircuitBreaker, CircuitOpenError, Pipeline

breaker = CircuitBreaker(name='recommendations')

pipeline = (
    Pipeline.builder()
    .fallback(lambda exc: [], on=(CircuitOpenError,))
    .retry(attempts=4)  # requires interlock-cb[tenacity]
    .circuit_breaker(breaker)
    .bulkhead(8)
    .timeout(2.0)
    .build()
)


@pipeline
async def fetch_picks(user: str) -> list[str]:
    return await client.get_picks(user)

Retries never hammer an open circuit, one hung attempt cannot eat the retry budget, and every decision is observable — see the pipeline guide.

httpx2 integration

Apply a breaker per host transparently, with no decorators in call sites:

import httpx2
from interlock.integrations.httpx2 import CircuitBreakerTransport

transport = CircuitBreakerTransport(httpx2.HTTPTransport())
client = httpx2.Client(transport=transport)

By default, transport exceptions and the canonical retryable statuses (429, 500, 502, 503, 504) count as failures; 4xx client errors do not.

FastAPI integration

Inject a per-name breaker with Depends and map an open circuit to a clean 503 with Retry-After:

from typing import Annotated

from fastapi import Depends, FastAPI
from interlock import CircuitBreaker, Registry
from interlock.integrations.fastapi import breaker_dependency, install_exception_handler

app = FastAPI()
registry = Registry()
install_exception_handler(app)

orders_db = breaker_dependency('orders-db', registry=registry)


@app.get('/orders')
async def orders(breaker: Annotated[CircuitBreaker, Depends(orders_db)]) -> list[dict]:
    return await breaker.call(fetch_orders)

Redis integration (shared state)

Coordinate breaker state across processes and machines: when one instance trips, every instance backs off, and recovery probes are budgeted globally. Redis failures never reach the protected call — the breaker degrades to local state and re-syncs when Redis recovers:

import redis
from interlock import CircuitBreaker, Registry
from interlock.integrations.redis import RedisStorage

storage = RedisStorage(redis.Redis(host='redis.internal'))
breaker = CircuitBreaker(name='payments', storage=storage)

registry = Registry(storage=storage)  # or share one storage across many breakers

Async services use AsyncRedisStorage with redis.asyncio.Redis the same way.

More integrations

The same per-host pattern ships for aiohttp (client middleware) and requests (session adapter), and the tenacity extra composes retries with the breaker correctly (stop retrying once the circuit opens, or wait exactly until the next probe). Recipes cover OpenAI / Anthropic SDK calls and Flask / Django handlers — see the integrations overview and the retries guide.

Documentation

The full documentation is hosted at https://bagowix.github.io/interlock/. The sources live in docs/:

Contributing

Bug reports and pull requests are welcome. See CONTRIBUTING.md for the local setup and the checks a change must pass, and CODE_OF_CONDUCT.md for community expectations. Security issues: please follow SECURITY.md.

License

interlock is released under the MIT License.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

interlock_cb-2.2.0.tar.gz (407.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

interlock_cb-2.2.0-py3-none-any.whl (68.2 kB view details)

Uploaded Python 3

File details

Details for the file interlock_cb-2.2.0.tar.gz.

File metadata

  • Download URL: interlock_cb-2.2.0.tar.gz
  • Upload date:
  • Size: 407.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for interlock_cb-2.2.0.tar.gz
Algorithm Hash digest
SHA256 830e39ba3c88776391f35f578fa08fc6c377d264d77b46ae5e648a6071656134
MD5 5bb6eb0c0eb45d139ad80746d1f99d06
BLAKE2b-256 930a5231cbed0126740a5e7f4aa244b5d6b97468d4d9cf66568c247ffb643101

See more details on using hashes here.

Provenance

The following attestation bundles were made for interlock_cb-2.2.0.tar.gz:

Publisher: release.yml on bagowix/interlock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file interlock_cb-2.2.0-py3-none-any.whl.

File metadata

  • Download URL: interlock_cb-2.2.0-py3-none-any.whl
  • Upload date:
  • Size: 68.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for interlock_cb-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 134b5f6ade5af523a4ca14f02cbb0861c673eeb27f69e95134efcd8e15ff73d9
MD5 e730e91f33e8c0a4e3e03450a1ef49b2
BLAKE2b-256 f3d2a00340e8c531a8cb0c2bc82c8c4220a2f8f80d5edd77e7c0dc2d3d5f78dd

See more details on using hashes here.

Provenance

The following attestation bundles were made for interlock_cb-2.2.0-py3-none-any.whl:

Publisher: release.yml on bagowix/interlock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

2.7.0

2 files

2.6.1

2 files

2.6.0

2 files

2.5.0

2 files

2.4.0

2 files

2.3.0

2 files

This release

2.2.0 This release

2 files

2.1.4

2 files

2.1.3

2 files

2.1.2

2 files

2.1.1

2 files

2.1.0

2 files

2.0.0

2 files

1.3.0

2 files

1.2.0

2 files

1.1.0

2 files

1.0.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page