55 lines
3.6 KiB
Markdown
55 lines
3.6 KiB
Markdown
# HTTPX source notes
|
|
|
|
Repo: `encode/httpx`
|
|
Local checkout: `/home/ubuntu/repos/rodin-sources/httpx`
|
|
|
|
## Why this repo is useful
|
|
- HTTPX is a strong source for modern boundary-design patterns in Python libraries: sync/async separation, transport seams, and caller-oriented exception design.
|
|
- It is especially useful because the same conceptual API is implemented twice (sync and async), making repeated design choices easy to spot.
|
|
|
|
## Sync and async APIs are parallel types, not a mode flag
|
|
|
|
### Repeated evidence
|
|
- `httpx/_client.py:594-660` defines `Client` as the synchronous entrypoint with `BaseTransport` and thread-sharing semantics.
|
|
- `httpx/_client.py:1307-1374` defines `AsyncClient` separately with the same broad constructor shape but `AsyncBaseTransport` and task-sharing semantics.
|
|
- `httpx/_client.py:688-696` and `httpx/_client.py:1402-1410` show the same transport-initialization flow in each class, reinforcing that the APIs are intentionally parallel rather than conditionally branching inside one type.
|
|
|
|
### Why it matters
|
|
Repeated signal: mature Python networking libraries keep sync and async usage obviously separate in the type system while preserving familiar parameter shapes. That lowers cognitive overhead without hiding execution-model differences.
|
|
|
|
### Caveat / counterexample
|
|
The pattern is not "duplicate everything." HTTPX keeps shared behavior in `BaseClient`; the duplication is at the public entrypoint where transport types and calling style genuinely differ.
|
|
|
|
## Exceptions are layered for catch-broadly / recover-narrowly behavior
|
|
|
|
### Repeated evidence
|
|
- `httpx/_exceptions.py:74-90` makes `HTTPError` the top-level catch point and explicitly documents `try/except httpx.HTTPError` as a supported usage pattern.
|
|
- `httpx/_exceptions.py:107-120` defines `RequestError` for failures that occur while issuing a request and explains why request context may be attached later.
|
|
- `httpx/_exceptions.py:123-160` narrows transport failures into `TransportError` and timeout-specific subclasses.
|
|
- `httpx/_exceptions.py:167-178` continues the layering into network-specific failures.
|
|
|
|
### Why it matters
|
|
Repeated signal: the exception tree is organized around what callers do next:
|
|
- catch one broad library exception for "request failed somehow"
|
|
- catch a narrower transport or timeout subtype for retry/backoff behavior
|
|
- still access `exc.request` when request context has been attached
|
|
|
|
## Testing and embedding happen at the transport boundary
|
|
|
|
### Repeated evidence
|
|
- `httpx/_transports/asgi.py:63-97` exposes `ASGITransport` as a first-class transport for routing requests directly into an ASGI app.
|
|
- `httpx/_transports/asgi.py:78-83` explicitly calls out `raise_app_exceptions=False` for testing 500 responses instead of surfacing app exceptions immediately.
|
|
- `httpx/_transports/mock.py:15-43` defines `MockTransport` as a shared sync/async seam that accepts a handler and adapts it through the transport interface.
|
|
|
|
### Why it matters
|
|
Repeated signal: HTTPX prefers substitutable transports over monkeypatching internal request code. That is a strong pattern for any client library that needs both real I/O and test-time embedding.
|
|
|
|
### Caveat / counterexample
|
|
Transport seams are great for boundary tests, but they are not a full replacement for end-to-end network tests. The strong pattern is to make the boundary swappable, not to pretend boundary tests cover every production behavior.
|
|
|
|
## Pattern candidates supported by this repo
|
|
- split sync and async public APIs into separate types
|
|
- keep constructor shapes parallel across sync/async variants
|
|
- design exception trees around recovery decisions
|
|
- expose transport seams for testing, embedding, and alternate runtimes
|