Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions architecture/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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`)

Expand Down
2 changes: 1 addition & 1 deletion architecture/extras.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
```

Expand Down
6 changes: 3 additions & 3 deletions docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
10 changes: 5 additions & 5 deletions docs/recipes/phase-decorator-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions planning/changes/active/2026-06-14.06-audit-doc-fixes/change.md
Original file line number Diff line number Diff line change
@@ -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).
4 changes: 3 additions & 1 deletion src/httpware/errors.py
Original file line number Diff line number Diff line change
@@ -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.

Expand Down