Skip to content

Commit dfd6f55

Browse files
authored
Merge pull request #52 from modern-python/fix/0.10.1-delta-audit
0.10.1 — delta-audit closure (fixes + hardening)
2 parents 2a2b541 + 1f272fa commit dfd6f55

12 files changed

Lines changed: 493 additions & 29 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ It does NOT pass through the middleware chain: `AsyncRetry`, `AsyncBulkhead`, an
112112

113113
## Errors
114114

115-
All 4xx/5xx responses raise typed exceptions automatically: `NotFoundError`, `ServiceUnavailableError`, `RateLimitedError`, etc. — all subclasses of `httpware.StatusError`. Transport-layer transient failures raise `NetworkError`; the resilience middleware raise `RetryBudgetExhaustedError` and `BulkheadFullError`. Everything inherits `httpware.ClientError`.
115+
All 4xx/5xx responses raise typed exceptions automatically: `NotFoundError`, `ServiceUnavailableError`, `RateLimitedError`, etc. — all subclasses of `httpware.StatusError`. Transport-layer transient failures raise `NetworkError`; the resilience middleware raise `RetryBudgetExhaustedError`, `BulkheadFullError`, and `CircuitOpenError`. Everything inherits `httpware.ClientError`.
116116

117117
## Observability
118118

@@ -126,7 +126,7 @@ import logging
126126
# Enable visibility into resilience operational events
127127
logging.getLogger("httpware.retry").setLevel(logging.WARNING)
128128
logging.getLogger("httpware.bulkhead").setLevel(logging.WARNING)
129-
logging.getLogger("httpware.circuit_breaker").setLevel(logging.WARNING)
129+
logging.getLogger("httpware.circuit_breaker").setLevel(logging.INFO) # INFO: includes recovery events (half_open, closed)
130130
logging.getLogger("httpware.timeout").setLevel(logging.WARNING)
131131
```
132132

docs/index.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,23 +138,34 @@ All errors inherit `httpware.ClientError`. The categories:
138138

139139
- **Status errors** (4xx/5xx responses) — raised automatically, no `raise_for_status()` needed: `NotFoundError`, `RateLimitedError`, `ServiceUnavailableError`, and the rest. All subclass `StatusError`.
140140
- **Transport errors** — connection / network / protocol failures before a response arrived. `NetworkError` (transient) subclasses `TransportError`.
141-
- **Resilience refusals**`RetryBudgetExhaustedError` and `BulkheadFullError`, raised by the resilience middleware.
141+
- **Resilience refusals**`RetryBudgetExhaustedError`, `BulkheadFullError`, and `CircuitOpenError`, raised by the resilience middleware.
142142
- **Decode errors**`DecodeError`, raised when `response_model=` decoding fails (HTTP call itself succeeded). `MissingDecoderError`, raised when no registered decoder claims the `response_model=` type — fires *before* the HTTP call.
143143

144144
See the [Errors reference](errors.md) for the full tree and catching strategies.
145145

146146
## Observability
147147

148-
`AsyncRetry`/`Retry` and `AsyncBulkhead`/`Bulkhead` emit operational events via two channels — stdlib `logging` records (always on) and OpenTelemetry span events (when `opentelemetry-api` is installed). Event names and payloads are identical across sync and async; dashboards built against one class apply unchanged to the other.
148+
All resilience middleware emit operational events via two channels — stdlib `logging` records (always on) and OpenTelemetry span events (when `opentelemetry-api` is installed). Event names and payloads are identical across sync and async; dashboards built against one class apply unchanged to the other.
149149

150-
Logger names (`httpware.retry`, `httpware.bulkhead`) and event names (`retry.giving_up`, `retry.budget_refused`, `retry.streaming_refused`, `bulkhead.rejected`) are the stable public contract.
150+
Logger names and event names are the stable public contract:
151+
152+
| Logger | Events |
153+
|---|---|
154+
| `httpware.retry` | `retry.giving_up`, `retry.budget_refused`, `retry.streaming_refused` |
155+
| `httpware.bulkhead` | `bulkhead.rejected` |
156+
| `httpware.circuit_breaker` | `circuit.opened` (WARNING), `circuit.rejected` (WARNING), `circuit.half_open` (INFO), `circuit.closed` (INFO) |
157+
| `httpware.timeout` | `timeout.exceeded` (WARNING) |
158+
159+
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.
151160

152161
```python
153162
import logging
154163

155-
# Enable visibility into retry / bulkhead operational events
164+
# Enable visibility into resilience operational events
156165
logging.getLogger("httpware.retry").setLevel(logging.WARNING)
157166
logging.getLogger("httpware.bulkhead").setLevel(logging.WARNING)
167+
logging.getLogger("httpware.circuit_breaker").setLevel(logging.INFO) # INFO for recovery events
168+
logging.getLogger("httpware.timeout").setLevel(logging.WARNING)
158169
```
159170

160171
For OTel attribute enrichment on the active span — install the extra:

docs/resilience.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ from httpware.middleware.resilience import AsyncRetry
2424
| `respect_retry_after` | `True` | When the response carries a `Retry-After` header on a retryable status, sleep for the header value instead of the jittered backoff. If the header value exceeds `max_delay`, AsyncRetry gives up and re-raises the underlying `StatusError` with a PEP 678 note `httpware: Retry-After (Ns) exceeded max_delay (Ms); giving up`. Set `max_delay` higher (or `respect_retry_after=False`) to opt out. |
2525
| `budget` | `RetryBudget()` (default-configured) | The token bucket. Pass a shared `RetryBudget` instance to apply one budget across multiple clients. |
2626

27-
For a whole-attempt wall-clock bound, use `httpx2.Timeout` on the client or
28-
pass `timeout=` per request. `httpware` does not own a structured-cancellation
29-
timeout knob.
27+
For a whole-operation wall-clock bound across all retry attempts, compose `AsyncTimeout` outermost — see [AsyncTimeout](#asynctimeout) below. For a per-request bound, use `httpx2.Timeout` on the client or pass `timeout=` per request.
3028

3129
### Retry-After parsing
3230

@@ -156,7 +154,7 @@ Classic consecutive-failure circuit breaker. Counts failures and prevents reques
156154
### States
157155

158156
- **CLOSED** — normal operation. Each counted failure increments the consecutive-failure counter. Once `failure_threshold` consecutive counted failures accumulate, the circuit opens.
159-
- **OPEN** — fast-fail. All requests are rejected immediately with `CircuitOpenError` (carrying `retry_after` seconds until the next probe window). After `reset_timeout` seconds the circuit moves to HALF_OPEN.
157+
- **OPEN** — fast-fail. While elapsed time is below `reset_timeout`, requests are rejected immediately with `CircuitOpenError` (carrying `retry_after` seconds until the next probe window). The first request after `reset_timeout` elapses transitions the circuit to HALF_OPEN and becomes the probe.
160158
- **HALF_OPEN** — exactly one probe is admitted. If `success_threshold` consecutive probe successes are observed, the circuit closes. A single probe failure re-opens the circuit.
161159

162160
### Constructor
@@ -317,7 +315,7 @@ from httpware.middleware.resilience import Retry
317315

318316
`Retry` uses `time.sleep` between attempts. `Retry-After`, streaming-body refusal, exhaustion behavior, and `RetryBudgetExhaustedError` semantics are identical to `AsyncRetry`.
319317

320-
For a whole-attempt wall-clock bound, use `httpx2.Timeout` on the wrapped client or pass `timeout=` per request. `httpware` does not own a structured-cancellation timeout knob.
318+
For a whole-attempt wall-clock bound, use `httpx2.Timeout` on the wrapped client or pass `timeout=` per request. No sync `Timeout` middleware exists — sync Python has no cancellation primitive that can interrupt a blocking call mid-flight.
321319

322320
### `Bulkhead`
323321

0 commit comments

Comments
 (0)