Skip to content

Commit daf6ac3

Browse files
lesnik512claude
andauthored
docs: close deep-audit documentation-accuracy findings (#66)
- architecture/client.md: document Client.stream() alongside AsyncClient.stream() (M4); attribute the httpx2 send to the internal _terminal, not .send() (L13). - errors.py docstring: list all four auto-raise sites, not just AsyncClient's terminal (Nit4). - architecture/extras.md: sync the pydantic constraint to >=2.0,<3.0 (Nit15). - docs/middleware.md + recipes/phase-decorator-patterns.md: import root-__all__ symbols via `from httpware import X` (Nit14). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5260f63 commit daf6ac3

6 files changed

Lines changed: 45 additions & 12 deletions

File tree

‎architecture/client.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## The internal terminal
66

7-
The bottom of the middleware chain (the "terminal") is internal. It calls `self._httpx2_client.send(request)`, maps `httpx2` errors to `httpware` errors, and raises a `StatusError` subclass on 4xx/5xx. The error-mapping table (what `httpx2` exception maps to which `httpware` exception) lives at the terminal in `src/httpware/client.py`; status-keyed exceptions are looked up via the `STATUS_TO_EXCEPTION` table in `src/httpware/errors.py`. The same terminal lifecycle holds in both worlds — `Client.send` calls `httpx2.Client.send`, `AsyncClient.send` calls `httpx2.AsyncClient.send`.
7+
The bottom of the middleware chain (the "terminal") is internal. It calls `self._httpx2_client.send(request)`, maps `httpx2` errors to `httpware` errors, and raises a `StatusError` subclass on 4xx/5xx. The error-mapping table (what `httpx2` exception maps to which `httpware` exception) lives at the terminal in `src/httpware/client.py`; status-keyed exceptions are looked up via the `STATUS_TO_EXCEPTION` table in `src/httpware/errors.py`. The same terminal lifecycle holds in both worlds: `Client.send` / `AsyncClient.send` enter the middleware chain first, and it is the internal terminal — `Client._terminal` / `AsyncClient._terminal` — that calls `httpx2.Client.send` / `httpx2.AsyncClient.send`.
88

99
## Sync/async parity
1010

@@ -14,7 +14,7 @@ The async middleware surface uses the `Async*`/`async_*` prefix, aligning with h
1414

1515
## Streaming
1616

17-
`AsyncClient.stream()` provides a context-manager API for chunked response bodies. It bypasses the middleware chain by design.
17+
Both `Client.stream()` (sync) and `AsyncClient.stream()` (async) provide a context-manager API for chunked response bodies. Both bypass the middleware chain by design.
1818

1919
## Proxy environment (`trust_env`)
2020

‎architecture/extras.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ A protocol seam is a documented internal boundary. AI agents and contributors mu
1515

1616
```toml
1717
[project.optional-dependencies]
18-
pydantic = ["pydantic>=2"]
18+
pydantic = ["pydantic>=2.0,<3.0"]
1919
msgspec = ["msgspec>=0.18"]
2020
```
2121

‎docs/middleware.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Whatever you do, return an `httpx2.Response`. Raising an exception propagates up
3838
For the common cases where you don't need state-keeping on `self` and don't need to wrap the full `await next(...)` call, `httpware.middleware` exports three decorators that turn a single async function into an `AsyncMiddleware`:
3939

4040
```python
41-
from httpware.middleware import async_before_request, async_after_response, async_on_error
41+
from httpware import async_before_request, async_after_response, async_on_error
4242
```
4343

4444
| Decorator | Function signature | When to use |
@@ -64,7 +64,7 @@ import uuid
6464
import httpx2
6565

6666
from httpware import AsyncClient, AsyncRetry
67-
from httpware.middleware import AsyncNext
67+
from httpware import AsyncNext
6868

6969

7070
_LOGGER = logging.getLogger("myapp.request_id")
@@ -152,7 +152,7 @@ import logging
152152
import httpx2
153153

154154
from httpware import Client
155-
from httpware.middleware import Next
155+
from httpware import Next
156156

157157

158158
_LOGGER = logging.getLogger("myapp.logging_middleware")

‎docs/recipes/phase-decorator-patterns.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The smallest useful case — add a static `Authorization` header to every outgoi
1414
import httpx2
1515

1616
from httpware import AsyncClient
17-
from httpware.middleware import async_before_request
17+
from httpware import async_before_request
1818

1919

2020
@async_before_request
@@ -43,7 +43,7 @@ import contextvars
4343
import httpx2
4444

4545
from httpware import AsyncClient, AsyncRetry
46-
from httpware.middleware import async_before_request
46+
from httpware import async_before_request
4747

4848

4949
_CORRELATION_ID: contextvars.ContextVar[str | None] = contextvars.ContextVar(
@@ -83,7 +83,7 @@ from collections.abc import Callable
8383
import httpx2
8484

8585
from httpware import AsyncClient
86-
from httpware.middleware import AsyncMiddleware, async_after_response
86+
from httpware import AsyncMiddleware, async_after_response
8787

8888

8989
MetricSink = Callable[[str, int], None]
@@ -126,8 +126,8 @@ When the upstream is unreachable, return a synthesized 503 with a sentinel heade
126126
import httpx2
127127

128128
from httpware import AsyncClient
129-
from httpware.errors import NetworkError
130-
from httpware.middleware import async_on_error
129+
from httpware import NetworkError
130+
from httpware import async_on_error
131131

132132

133133
@async_on_error
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
status: draft
3+
date: 2026-06-14
4+
slug: audit-doc-fixes
5+
supersedes: null
6+
superseded_by: null
7+
pr: null
8+
outcome: null
9+
---
10+
11+
# Change: Deep-audit documentation accuracy fixes
12+
13+
**Lane:** docs/docstring-only; spec is the [2026-06-14 deep audit](../../../audits/2026-06-14-deep-audit.md).
14+
15+
## Goal
16+
17+
Close the remaining confirmed documentation-accuracy findings.
18+
19+
## Findings
20+
21+
- **M4** — `architecture/client.md` streaming section omitted `Client.stream()` → now documents both `Client.stream()` and `AsyncClient.stream()`.
22+
- **L13** — `architecture/client.md` attributed the `httpx2.*.send` call to `Client.send`/`AsyncClient.send` → corrected to the internal `_terminal` (and `.send()` enters the chain first).
23+
- **Nit4** — `src/httpware/errors.py` module docstring attributed auto-raise to `AsyncClient`'s terminal only → now lists all four raise sites (both terminals + both `stream()` methods).
24+
- **Nit15** — `architecture/extras.md` showed `pydantic>=2` without the `<3.0` ceiling → synced to `pydantic>=2.0,<3.0` (matches `pyproject.toml`).
25+
- **Nit14** — `docs/middleware.md` and `docs/recipes/phase-decorator-patterns.md` imported root-`__all__` symbols via submodule paths → standardized to `from httpware import X`.
26+
27+
(Nit2 — `_is_streaming_body_async` reliance note — moot: PR #64 symmetrized the code instead.)
28+
29+
## Verification
30+
31+
- [ ] `just lint-ci` clean (CI-exact, no autofix); `just test` 100% (docstring change adds no lines).

‎src/httpware/errors.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Status-keyed exception hierarchy.
22
3-
Auto-raise rule lives at AsyncClient's internal terminal (see client.py).
3+
Auto-raise fires at four sites (all in client.py): both clients' internal
4+
terminals (Client._terminal / AsyncClient._terminal) and both stream() methods
5+
(Client.stream / AsyncClient.stream).
46
Unknown 4xx falls back to ClientStatusError; unknown 5xx to ServerStatusError.
57
The fallback assumes 400 <= status < 600.
68

0 commit comments

Comments
 (0)