|
7 | 7 |
|
8 | 8 | **Async HTTP client framework for Python.** |
9 | 9 |
|
10 | | -`httpware` is a thin opinionated wrapper around `httpx2`. It re-exports `httpx2.Request`/`httpx2.Response`, adds a middleware chain composed at client construction, supports opt-in typed response decoding (pydantic and msgspec are both extras), and raises a status-keyed exception tree automatically on 4xx/5xx. |
| 10 | +`httpware` is a thin opinionated wrapper around `httpx2`. It re-exports `httpx2.Request`/`httpx2.Response`, adds a middleware chain composed at client construction, supports opt-in typed response decoding (pydantic and msgspec are both extras), and raises a status-keyed exception tree automatically on 4xx/5xx. It also ships a small resilience suite — `Retry` middleware with a Finagle-style `RetryBudget`, plus a `Bulkhead` concurrency limiter — under `httpware.middleware.resilience`. |
11 | 11 |
|
12 | | -> **Status:** Pre-1.0 (0.3.0). Public API is subject to change between minor releases until v1.0. Resilience middleware (retry / timeout / bulkhead), streaming, and observability are not yet shipped. |
| 12 | +> **Status:** Pre-1.0. Public API is subject to change between minor releases until v1.0. Streaming and observability are not yet shipped. |
13 | 13 |
|
14 | 14 | ## Install |
15 | 15 |
|
@@ -42,7 +42,30 @@ async def main() -> None: |
42 | 42 | print(user.name) |
43 | 43 | ``` |
44 | 44 |
|
45 | | -## 📚 [Documentation](https://httpware.readthedocs.io) |
| 45 | +### With resilience middleware |
| 46 | + |
| 47 | +Compose resilience middleware at construction; `Bulkhead` goes outside `Retry` so one slot covers all retry attempts. |
| 48 | + |
| 49 | +```python |
| 50 | +from httpware import AsyncClient, Bulkhead, Retry |
| 51 | + |
| 52 | + |
| 53 | +async def main() -> None: |
| 54 | + async with AsyncClient( |
| 55 | + base_url="https://api.example.com", |
| 56 | + middleware=[ |
| 57 | + Bulkhead(max_concurrent=10), # cap total in-flight |
| 58 | + Retry(), # default: 3 attempts, full-jitter backoff |
| 59 | + ], |
| 60 | + ) as client: |
| 61 | + user = await client.get("/users/1", response_model=User) |
| 62 | +``` |
| 63 | + |
| 64 | +## Errors |
| 65 | + |
| 66 | +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`. |
| 67 | + |
| 68 | +## 🗒️ [Release notes](https://github.com/modern-python/httpware/releases) |
46 | 69 |
|
47 | 70 | ## 📦 [PyPI](https://pypi.org/project/httpware) |
48 | 71 |
|
|
0 commit comments