3.6 KiB
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-660definesClientas the synchronous entrypoint withBaseTransportand thread-sharing semantics.httpx/_client.py:1307-1374definesAsyncClientseparately with the same broad constructor shape butAsyncBaseTransportand task-sharing semantics.httpx/_client.py:688-696andhttpx/_client.py:1402-1410show 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-90makesHTTPErrorthe top-level catch point and explicitly documentstry/except httpx.HTTPErroras a supported usage pattern.httpx/_exceptions.py:107-120definesRequestErrorfor failures that occur while issuing a request and explains why request context may be attached later.httpx/_exceptions.py:123-160narrows transport failures intoTransportErrorand timeout-specific subclasses.httpx/_exceptions.py:167-178continues 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.requestwhen request context has been attached
Testing and embedding happen at the transport boundary
Repeated evidence
httpx/_transports/asgi.py:63-97exposesASGITransportas a first-class transport for routing requests directly into an ASGI app.httpx/_transports/asgi.py:78-83explicitly calls outraise_app_exceptions=Falsefor testing 500 responses instead of surfacing app exceptions immediately.httpx/_transports/mock.py:15-43definesMockTransportas 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