Skip to content

Commit 2a9aac1

Browse files
authored
Merge pull request #25 from modern-python/docs/sync-0.4
docs: sync user docs with shipped 0.4 features (3-6)
2 parents 24b03af + 153124a commit 2a9aac1

5 files changed

Lines changed: 64 additions & 11 deletions

File tree

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
**Async HTTP client framework for Python.**
99

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`.
1111

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.
1313
1414
## Install
1515

@@ -42,7 +42,30 @@ async def main() -> None:
4242
print(user.name)
4343
```
4444

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)
4669

4770
## 📦 [PyPI](https://pypi.org/project/httpware)
4871

docs/dev/contributing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ just test # pytest with coverage
3131

3232
These are enforced by CI grep gates. Do not break them in pull requests:
3333

34-
- No `import httpx2` outside `src/httpware/transports/httpx2.py`.
3534
- No `httpx2._*` (private API) usage anywhere in the library.
3635
- No `from __future__ import annotations`.
3736
- No `print()` calls.
3837
- No `logging.basicConfig()` or bare `logging.getLogger()`.
38+
- Type suppressions use `# ty: ignore[<rule>]`, never `# type: ignore` or `# mypy: ignore`.
3939

4040
## Code of Conduct
4141

docs/index.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# httpware
22

3-
A Python async HTTP client framework for building resilient service clients. `httpware` owns the abstraction layer above the underlying HTTP client (`httpx2` by default); consumers never import the transport directly.
3+
A Python async HTTP client framework for building resilient service clients. `httpware` is a thin opinionated wrapper around `httpx2` — it re-exports `httpx2.Request`/`httpx2.Response` as the public request/response surface, adds a middleware chain (with a built-in resilience suite: `Retry` + `RetryBudget`, `Bulkhead`), opt-in typed response decoding, and a status-keyed exception tree raised automatically on 4xx/5xx.
44

5-
> **Status:** Pre-1.0 (0.1.0 alpha). Public API is subject to change between minor releases until v1.0.
5+
> **Status:** Pre-1.0. Public API is subject to change between minor releases until v1.0. Streaming and observability are not yet shipped.
66
77
## Install
88

@@ -13,6 +13,7 @@ pip install httpware
1313
Optional extras:
1414

1515
```bash
16+
pip install httpware[pydantic] # PydanticDecoder (the default decoder path)
1617
pip install httpware[msgspec] # MsgspecDecoder
1718
```
1819

@@ -39,10 +40,34 @@ async def main() -> None:
3940
asyncio.run(main())
4041
```
4142

43+
### With resilience middleware
44+
45+
Compose resilience middleware at construction; `Bulkhead` goes outside `Retry` so one slot covers all retry attempts.
46+
47+
```python
48+
from httpware import AsyncClient, Bulkhead, Retry
49+
50+
51+
async def main() -> None:
52+
async with AsyncClient(
53+
base_url="https://api.example.com",
54+
middleware=[
55+
Bulkhead(max_concurrent=10), # cap total in-flight
56+
Retry(), # default: 3 attempts, full-jitter backoff
57+
],
58+
) as client:
59+
user = await client.get("/users/1", response_model=User)
60+
```
61+
62+
## Errors
63+
64+
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`.
65+
4266
## Where to go next
4367

44-
- **[Engineering Notes](dev/engineering.md)** — design invariants, the five protocol seams, exception contract, module layout, testing patterns, optional-extras pattern.
68+
- **[Engineering Notes](https://github.com/modern-python/httpware/blob/main/planning/engineering.md)** — design invariants, the three protocol seams, exception contract, module layout, testing patterns, optional-extras pattern. Lives in the repo at `planning/engineering.md`.
4569
- **[Contributing](dev/contributing.md)** — setup, conventions, workflow.
70+
- **[Release notes](https://github.com/modern-python/httpware/releases)** — per-version changelogs.
4671

4772
## Part of `modern-python`
4873

mkdocs.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ edit_uri: edit/main/docs/
77
nav:
88
- Quick-Start: index.md
99
- Development:
10-
- Engineering Notes: dev/engineering.md
1110
- Contributing: dev/contributing.md
1211

1312
theme:

planning/engineering.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This doc is the single distilled reference for `httpware` design rationale, prot
44

55
## 1. Project intent
66

7-
`httpware` is a thin opinionated wrapper around `httpx2`. It re-exports `httpx2.Request` and `httpx2.Response` as the public request/response surface and adds three things on top: typed response decoding (via a `ResponseDecoder` protocol; pydantic and msgspec are both opt-in extras as of 0.3.0), a middleware chain composed at client construction, and a status-keyed exception tree raised automatically on 4xx and 5xx. `AsyncClient(decoder=None)` defaults to constructing a `PydanticDecoder` and so requires the `pydantic` extra; callers can supply an explicit `decoder=` argument to escape the default.
7+
`httpware` is a thin opinionated wrapper around `httpx2`. It re-exports `httpx2.Request` and `httpx2.Response` as the public request/response surface and adds three things on top: typed response decoding (via a `ResponseDecoder` protocol; pydantic and msgspec are both opt-in extras as of 0.3.0), a middleware chain composed at client construction, and a status-keyed exception tree raised automatically on 4xx and 5xx. `AsyncClient(decoder=None)` defaults to constructing a `PydanticDecoder` and so requires the `pydantic` extra; callers can supply an explicit `decoder=` argument to escape the default. As of 0.4.0, the package ships a small resilience suite under `httpware.middleware.resilience` — a `Retry` middleware with a Finagle-style `RetryBudget`, plus a `Bulkhead` concurrency limiter — composed via the standard middleware chain.
88

99
The 0.1.0 release attempted to own a full abstraction over the underlying HTTP client. v0.2 walks that back: `httpx2` is part of the public surface.
1010

@@ -70,10 +70,16 @@ src/httpware/
7070
├── __init__.py # public exports
7171
├── py.typed
7272
├── client.py # AsyncClient
73-
├── errors.py # status-keyed exception tree (response: httpx2.Response)
73+
├── errors.py # status-keyed exception tree + NetworkError + RetryBudgetExhaustedError + BulkheadFullError
7474
├── middleware/
7575
│ ├── __init__.py # Middleware protocol, Next type, @before_request/@after_response/@on_error
76-
│ └── chain.py # compose(middleware, terminal) -> Next
76+
│ ├── chain.py # compose(middleware, terminal) -> Next
77+
│ └── resilience/
78+
│ ├── __init__.py # re-exports Bulkhead, Retry, RetryBudget
79+
│ ├── bulkhead.py # Bulkhead middleware (concurrency limiter)
80+
│ ├── budget.py # RetryBudget (Finagle-style token bucket)
81+
│ ├── retry.py # Retry middleware
82+
│ └── _backoff.py # full-jitter exponential backoff helper (private)
7783
├── decoders/
7884
│ ├── __init__.py # ResponseDecoder protocol
7985
│ ├── pydantic.py # PydanticDecoder (extra: pydantic)

0 commit comments

Comments
 (0)