# 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