Skip to content

Commit c13685a

Browse files
lesnik512claude
andcommitted
docs(client): document per-verb *_with_response siblings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 67ec0b9 commit c13685a

3 files changed

Lines changed: 59 additions & 4 deletions

File tree

architecture/client.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ The sync and async surfaces are kept at parity. Shared state is thread-safe wher
1212

1313
The async middleware surface uses the `Async*`/`async_*` prefix, aligning with httpx2's convention.
1414

15+
## `send_with_response` and per-verb siblings
16+
17+
`send_with_response(request, *, response_model)` returns `(httpx2.Response, T)` atomically — the decoded body and the raw response together. This is the building block for cases where response metadata (headers, status) is needed alongside the typed body, such as Link-header pagination.
18+
19+
The per-verb `*_with_response` siblings — `get_with_response`, `post_with_response`, `put_with_response`, `patch_with_response`, `delete_with_response`, and `request_with_response` — are the one-call ergonomic form: `response_model` is required, they return `tuple[httpx2.Response, T]`, and they accept the same keyword arguments as their non-`_with_response` counterparts; there is no `head_with_response` or `options_with_response` — use `request_with_response` for those methods.
20+
1521
## Streaming
1622

1723
Both `Client.stream()` (sync) and `AsyncClient.stream()` (async) provide a context-manager API for chunked response bodies. Both bypass the middleware chain by design.

docs/recipes/link-header-pagination.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,26 @@ async def main() -> None:
3030

3131
`process` and `next_link` are caller-defined. Pick a Link-header parser that fits your project — there are several on PyPI, and the format is small enough to hand-roll.
3232

33+
## Shorthand: per-verb `*_with_response`
34+
35+
When you do not need a pre-built `Request` object, the per-verb siblings collapse the `build_request` + `send_with_response` two-step into a single call:
36+
37+
```python
38+
# two-step (pre-built request, required when you need full Request control)
39+
request = client.build_request("GET", url, params=params)
40+
response, tags = await client.send_with_response(request, response_model=list[Tag])
41+
42+
# one-call shorthand (equivalent for the simple case)
43+
response, tags = await client.get_with_response(url, params=params, response_model=list[Tag])
44+
```
45+
46+
The full set of siblings is `get_with_response`, `post_with_response`, `put_with_response`, `patch_with_response`, `delete_with_response`, and `request_with_response`. There is no `head_with_response` or `options_with_response` — use `request_with_response` for those methods.
47+
3348
## When to use which API
3449

3550
- **Body only, high-level verb:** `client.get(..., response_model=...)`
3651
- **Body only, custom `Request`:** `client.send(request, response_model=...)`
37-
- **Body + response metadata:** `client.send_with_response(request, response_model=...)`
52+
- **Body + response metadata, simple URL:** `client.get_with_response(url, response_model=...)`
53+
- **Body + response metadata, pre-built `Request`:** `client.send_with_response(request, response_model=...)`
3854

39-
`send_with_response` is not for streaming responses — use [`stream()`](../index.md#streaming-responses) for those.
55+
`send_with_response` and the `*_with_response` siblings are not for streaming responses — use [`stream()`](../index.md#streaming-responses) for those.

tests/test_client_typing.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
"""Static-typing tests for AsyncClient and Client overloads.
22
3-
These assert overload selection at runtime via isinstance checks. ty/mypy
4-
catches the static-typing variant during `just lint`.
3+
Assertions come in two forms:
4+
- Runtime ``isinstance`` checks that verify the overload selected at call
5+
time (send / send_with_response with and without response_model).
6+
- Static ``typing.assert_type`` calls that let ty/mypy verify the inferred
7+
return type during ``just lint``.
58
"""
69

10+
import typing
711
from http import HTTPStatus
812

913
import httpx2
@@ -92,3 +96,32 @@ def test_sync_send_with_response_model_returns_typed() -> None:
9296
client = Client(httpx2_client=httpx2.Client(transport=transport))
9397
result = client.send(httpx2.Request("GET", "https://example.test/x"), response_model=_User)
9498
assert isinstance(result, _User)
99+
100+
101+
# ---------------------------------------------------------------------------
102+
# *_with_response static-type assertions — validates tuple[Response, T] destructuring
103+
# ---------------------------------------------------------------------------
104+
105+
106+
async def test_get_with_response_return_type() -> None:
107+
transport = httpx2.MockTransport(
108+
lambda req: httpx2.Response(HTTPStatus.OK, request=req, json={"id": 1, "name": "a"})
109+
)
110+
client = AsyncClient(httpx2_client=httpx2.AsyncClient(transport=transport))
111+
response, user = await client.get_with_response("https://example.test/x", response_model=_User)
112+
typing.assert_type(response, httpx2.Response)
113+
typing.assert_type(user, _User)
114+
assert isinstance(response, httpx2.Response)
115+
assert isinstance(user, _User)
116+
117+
118+
def test_sync_get_with_response_return_type() -> None:
119+
transport = httpx2.MockTransport(
120+
lambda req: httpx2.Response(HTTPStatus.OK, request=req, json={"id": 1, "name": "a"})
121+
)
122+
client = Client(httpx2_client=httpx2.Client(transport=transport))
123+
response, user = client.get_with_response("https://example.test/x", response_model=_User)
124+
typing.assert_type(response, httpx2.Response)
125+
typing.assert_type(user, _User)
126+
assert isinstance(response, httpx2.Response)
127+
assert isinstance(user, _User)

0 commit comments

Comments
 (0)