Files
python-patterns/sources/httpx.md
T
2026-06-01 21:42:05 +00:00

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-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