mrkeyoor.com_
Sat 19 Sept 06:40 UTC
PyPIWeb Backendupdated 19 Sept 2026

httpx review

HTTPX 0.28.1 is a Python HTTP client with closely matched synchronous and asynchronous interfaces. It uses Client and AsyncClient for connection pooling, supports HTTP/1.1 and optional HTTP/2, applies timeouts by default, and can call ASGI or WSGI applications through in-process transports. The request and response style is intentionally familiar to Requests users, but the transport model also exposes pool limits, proxies, streaming, custom authentication flows, and mock transports. HTTP/2 support requires an extra install and an explicit client option.

Verdict

HTTPX is the strongest general choice for async Python calls and in-process ASGI tests. Pin 0.28.x carefully and add deliberate response-retry policy when the service contract needs it.

We installed it

Lab card: what happened when we installed httpxScreenshot of httpx documentation
Install✓ · 0.2s7 packages on disk · 3 MB
Importimport httpx in 0.37s · pure Python · py.typed · requires Python >=3.8
Known vulns0(pip-audit)

Answers from our run

Does httpx install cleanly?

Yes. In a fresh container with an empty cache, pip install httpx finished in 0.2s, leaving 7 packages and 3 MB on disk. pip-audit reported no known vulnerabilities.

What does httpx need to run?

Python >=3.8, and nothing compiled: it is pure Python. In our run import httpx succeeded in 0.37s, and the package ships py.typed for type checkers.

httpx or requests: which should you use?

requests: Use it for synchronous applications that value a settled API and the widest existing examples. HTTPX is the strongest general choice for async Python calls and in-process ASGI tests.

When should you not use httpx?

A stable 1.x compatibility promise is mandatory; the project remains on 0.28 and has removed deprecated arguments in minor releases

API stability3/5The everyday request, response, Client, and AsyncClient APIs are well established, but the project is still pre-1.0. Minor releases have completed removals of deprecated app and proxies arguments, forcing downstream test and proxy code to change. Pinning and reading the changelog remain appropriate even when only the second version component moves.
Docs5/5The project site separates quickstart, async use, resource limits, timeouts, HTTP/2, proxies, SSL, authentication, transports, and Requests compatibility. Examples cover both sync and async context management, and the exceptions hierarchy is documented. The transport pages also state important limits such as ASGI lifespan events not being triggered automatically.
Maintenance3/5PyPI still lists 0.28.1, while GitHub showed the last repository push on 2026-03-29 and 143 open issues and pull requests. Work remains visible, but the gap between repository activity and a newer published release makes fixes and compatibility changes less predictable for users. That slower cadence matters more while the public version remains below 1.0.
Ecosystem4/5PyPI Stats recorded 205,114,231 downloads in the last week, and GitHub showed 15,429 stars. FastAPI and Starlette testing guidance commonly uses HTTPX, respx provides request mocking, and optional packages add HTTP/2, SOCKS, and command-line support. Requests still has a larger legacy integration footprint, but HTTPX is established in modern ASGI stacks.

Discussed on

  1. hnHttpx: A next-generation HTTP client for Python463 points
  2. hnWhy I forked httpx252 points
  3. hnHttpx: A Ruby HTTP library157 points

Use it if

  • An asyncio application needs an awaitable client rather than moving Requests calls into threads
  • Tests should call an ASGI application in-process without listening on a network port
  • One codebase needs similar sync and async request APIs with typed package declarations
  • Pool timeouts, transport replacement, or optional HTTP/2 need to be configured directly
Skip it if

Setup reality

HTTPX 0.28.1 installed in 0.2 seconds in our fresh Python 3.12 environment. Seven packages used 3 MB on disk. The distribution declares 12 direct dependencies, is pure Python, supports Python 3.8 or newer, uses BSD-3-Clause, and ships py.typed. pip-audit reported no known vulnerabilities. import httpx completed in 0.37 seconds.

Top-level httpx.get() opens a short-lived connection context. Reuse Client or AsyncClient for repeated calls so pooling can do its job, and close it through a context manager. Do not create an AsyncClient inside a hot loop. The default timeout applies separate connect, read, write, and pool phases; tune them rather than passing one huge number or disabling every timeout.

HTTP/2 needs httpx[http2] plus http2=True, and the server must negotiate it. SOCKS support is another extra. In 0.28, use proxy= for a single proxy or mounts= for per-scheme routing; older examples using proxies= are stale. TLS verification is on by default. Supply a documented SSLContext when custom trust roots are required instead of setting verify=False.

Async streaming must remain inside the response context or be closed manually. ASGITransport calls the app but does not run lifespan events for it, so tests needing startup and shutdown should add a lifespan manager. HTTPTransport(retries=N) retries connect failures and connect timeouts, not 429 or 503 responses. Application-level retry code must restrict methods, honor Retry-After when relevant, and bound the total deadline.

Patterns

Fetch JSON and reject an HTTP error simple-get

import httpx

r = httpx.get('https://api.example.com/items', params={'page': 2})
r.raise_for_status()
data = r.json()

raise_for_status separates unsuccessful HTTP responses from successful responses before JSON parsing.

Reuse one pool across related calls client-reuse

import httpx

with httpx.Client(base_url='https://api.example.com',
                  headers={'Authorization': f'Bearer {token}'}) as client:
    a = client.get('/users/1')
    b = client.get('/users/2')

A Client keeps connections and shared headers. Close it with a context manager instead of constructing one per request.

Send concurrent requests with AsyncClient async-requests

import asyncio
import httpx

async def main():
    async with httpx.AsyncClient() as client:
        results = await asyncio.gather(
            client.get('https://api.example.com/a'),
            client.get('https://api.example.com/b'),
        )
    return [r.json() for r in results]

asyncio.run(main())

Create the async client outside repeated tasks and close it after gathered work completes.

Send a JSON request body post-json

r = client.post('https://api.example.com/items',
                json={'name': 'widget', 'qty': 3})
r.raise_for_status()

json sets serialization and content type. raise_for_status is still needed if non-2xx responses should become exceptions.

Give each timeout phase a budget timeouts

timeout = httpx.Timeout(10.0, connect=5.0)
client = httpx.Client(timeout=timeout)

# or disable for a long streaming call
client.get(url, timeout=None)

HTTPX has connect, read, write, and pool timeouts. timeout=None removes protection and should be limited to a known streaming case.

Separate timeout, network, and status failures error-handling

try:
    r = client.get(url)
    r.raise_for_status()
except httpx.TimeoutException:
    ...  # connect/read/write/pool timeout
except httpx.HTTPStatusError as e:
    print(e.response.status_code, e.response.text)
except httpx.RequestError as e:
    print(f'network problem talking to {e.request.url}')

HTTPStatusError has a response. RequestError covers transport failures, while timeout subclasses identify the exhausted phase.

Stream a download without buffering it all streaming-download

with client.stream('GET', 'https://example.com/big.iso') as r:
    r.raise_for_status()
    with open('big.iso', 'wb') as f:
        for chunk in r.iter_bytes(chunk_size=65536):
            f.write(chunk)

Keep iteration inside the stream context so the response closes and returns its connection to the pool.

Retry connection establishment connect-retries

transport = httpx.HTTPTransport(retries=3)
client = httpx.Client(transport=transport)

Transport retries do not retry 429, 500, or other completed HTTP responses. Add bounded application policy for those.

Alternatives

PackageRegistryPick it when
requestsPyPIUse it for synchronous applications that value a settled API and the widest existing examples.
aiohttpPyPIUse it for high-concurrency asyncio workloads or when the same project must also provide a server.
urllib3PyPIUse it for lower-level pooling and its configurable Retry behavior without a session-style facade.

More web backend guides

urllib3 · requests · ws · anyio · undici · express · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.