diff --git a/architecture/client.md b/architecture/client.md index 73c446e..74001a2 100644 --- a/architecture/client.md +++ b/architecture/client.md @@ -4,7 +4,7 @@ ## The internal terminal -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`. +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`. ## Sync/async parity @@ -14,7 +14,7 @@ The async middleware surface uses the `Async*`/`async_*` prefix, aligning with h ## Streaming -`AsyncClient.stream()` provides a context-manager API for chunked response bodies. It bypasses the middleware chain by design. +Both `Client.stream()` (sync) and `AsyncClient.stream()` (async) provide a context-manager API for chunked response bodies. Both bypass the middleware chain by design. ## Proxy environment (`trust_env`) diff --git a/architecture/extras.md b/architecture/extras.md index 2cb34ba..1780a06 100644 --- a/architecture/extras.md +++ b/architecture/extras.md @@ -15,7 +15,7 @@ A protocol seam is a documented internal boundary. AI agents and contributors mu ```toml [project.optional-dependencies] -pydantic = ["pydantic>=2"] +pydantic = ["pydantic>=2.0,<3.0"] msgspec = ["msgspec>=0.18"] ``` diff --git a/docs/middleware.md b/docs/middleware.md index 0549de0..0ce2a3b 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -38,7 +38,7 @@ Whatever you do, return an `httpx2.Response`. Raising an exception propagates up 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`: ```python -from httpware.middleware import async_before_request, async_after_response, async_on_error +from httpware import async_before_request, async_after_response, async_on_error ``` | Decorator | Function signature | When to use | @@ -64,7 +64,7 @@ import uuid import httpx2 from httpware import AsyncClient, AsyncRetry -from httpware.middleware import AsyncNext +from httpware import AsyncNext _LOGGER = logging.getLogger("myapp.request_id") @@ -152,7 +152,7 @@ import logging import httpx2 from httpware import Client -from httpware.middleware import Next +from httpware import Next _LOGGER = logging.getLogger("myapp.logging_middleware") diff --git a/docs/recipes/phase-decorator-patterns.md b/docs/recipes/phase-decorator-patterns.md index ff6b432..81a0549 100644 --- a/docs/recipes/phase-decorator-patterns.md +++ b/docs/recipes/phase-decorator-patterns.md @@ -14,7 +14,7 @@ The smallest useful case — add a static `Authorization` header to every outgoi import httpx2 from httpware import AsyncClient -from httpware.middleware import async_before_request +from httpware import async_before_request @async_before_request @@ -43,7 +43,7 @@ import contextvars import httpx2 from httpware import AsyncClient, AsyncRetry -from httpware.middleware import async_before_request +from httpware import async_before_request _CORRELATION_ID: contextvars.ContextVar[str | None] = contextvars.ContextVar( @@ -83,7 +83,7 @@ from collections.abc import Callable import httpx2 from httpware import AsyncClient -from httpware.middleware import AsyncMiddleware, async_after_response +from httpware import AsyncMiddleware, async_after_response MetricSink = Callable[[str, int], None] @@ -126,8 +126,8 @@ When the upstream is unreachable, return a synthesized 503 with a sentinel heade import httpx2 from httpware import AsyncClient -from httpware.errors import NetworkError -from httpware.middleware import async_on_error +from httpware import NetworkError +from httpware import async_on_error @async_on_error diff --git a/planning/changes/active/2026-06-14.06-audit-doc-fixes/change.md b/planning/changes/active/2026-06-14.06-audit-doc-fixes/change.md new file mode 100644 index 0000000..68c0da1 --- /dev/null +++ b/planning/changes/active/2026-06-14.06-audit-doc-fixes/change.md @@ -0,0 +1,31 @@ +--- +status: draft +date: 2026-06-14 +slug: audit-doc-fixes +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Change: Deep-audit documentation accuracy fixes + +**Lane:** docs/docstring-only; spec is the [2026-06-14 deep audit](../../../audits/2026-06-14-deep-audit.md). + +## Goal + +Close the remaining confirmed documentation-accuracy findings. + +## Findings + +- **M4** — `architecture/client.md` streaming section omitted `Client.stream()` → now documents both `Client.stream()` and `AsyncClient.stream()`. +- **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). +- **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). +- **Nit15** — `architecture/extras.md` showed `pydantic>=2` without the `<3.0` ceiling → synced to `pydantic>=2.0,<3.0` (matches `pyproject.toml`). +- **Nit14** — `docs/middleware.md` and `docs/recipes/phase-decorator-patterns.md` imported root-`__all__` symbols via submodule paths → standardized to `from httpware import X`. + +(Nit2 — `_is_streaming_body_async` reliance note — moot: PR #64 symmetrized the code instead.) + +## Verification + +- [ ] `just lint-ci` clean (CI-exact, no autofix); `just test` 100% (docstring change adds no lines). diff --git a/src/httpware/errors.py b/src/httpware/errors.py index 05d44e6..d14f255 100644 --- a/src/httpware/errors.py +++ b/src/httpware/errors.py @@ -1,6 +1,8 @@ """Status-keyed exception hierarchy. -Auto-raise rule lives at AsyncClient's internal terminal (see client.py). +Auto-raise fires at four sites (all in client.py): both clients' internal +terminals (Client._terminal / AsyncClient._terminal) and both stream() methods +(Client.stream / AsyncClient.stream). Unknown 4xx falls back to ClientStatusError; unknown 5xx to ServerStatusError. The fallback assumes 400 <= status < 600.