You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ad832f8
Browse filesBrowse the repository at this point in the historyBrowse files
Each log record carries an `event` field with the event-name string (e.g. `event="circuit.opened"`), usable for log-aggregator filtering. See [resilience.md](resilience.md) for the full event tables per middleware.
16
+
Each log record carries an `event` field with the event-name string (e.g. `event="circuit.opened"`), usable for log-aggregator filtering. Events from `AsyncKeyedCircuitBreaker` / `KeyedCircuitBreaker` also carry `circuit_key`, naming the circuit they belong to. See [resilience.md](resilience.md) for the full event tables per middleware.
from httpware.middleware.resilience import AsyncKeyedCircuitBreaker # async
261
+
from httpware.middleware.resilience import KeyedCircuitBreaker # sync
262
+
```
263
+
264
+
One independent circuit per **circuit key**, for a client whose requests go to more than one upstream. A plain `AsyncCircuitBreaker` holds a single circuit, so one failing upstream would fast-fail requests to every healthy one; the keyed breaker opens only the failing upstream's circuit.
265
+
266
+
Each circuit behaves exactly like an [`AsyncCircuitBreaker`](#asynccircuitbreaker-circuitbreaker) built with the same arguments: same states, failure classification, rate mode, half-open probe and events. The probe slot is per circuit, so two upstreams recovering at once each get their own probe.
267
+
268
+
### Constructor
269
+
270
+
Every `AsyncCircuitBreaker` parameter, with the same defaults, plus:
271
+
272
+
| Parameter | Default | Effect |
273
+
|---|---|---|
274
+
|`key`|`request.url.origin`| Maps a request to its circuit key. Any hashable value works. The default origin combines scheme, host and port, normalized, so `https://A.example/x` and `https://a.example:443/y` share a circuit while `http://a.example` and `https://a.example:8443` each get their own. |
275
+
276
+
### Circuit lifetime
277
+
278
+
A circuit is created on the first request for its key and kept for as long as the breaker lives; nothing is evicted or pruned. Memory grows with the number of distinct keys, so `key` must map to a small, bounded set — configured upstreams, not a value taken from user input. A key function that returns something unbounded, such as the full URL, grows the map forever.
279
+
280
+
### Observability
281
+
282
+
The same events as `AsyncCircuitBreaker`, on the same `httpware.circuit_breaker` logger, each carrying one extra attribute: `circuit_key`, the `str()` of the request's circuit key.
283
+
284
+
### Example
285
+
286
+
```python
287
+
from httpware import AsyncClient
288
+
from httpware.middleware.resilience import AsyncKeyedCircuitBreaker, AsyncRetry
await client.get("https://suggest-b.example/v1/suggest") # unaffected if suggest-a is down
296
+
```
297
+
298
+
The keyed breaker takes the circuit breaker's place in the [composition](#composition): outside `AsyncRetry`, so it counts one outcome per retry sequence. Sync usage is identical: `Client` + `KeyedCircuitBreaker`, no `await`.
0 commit comments