requests, httpx, and aiohttp
coreintermediaterequests is the classic synchronous HTTP client — one blocking call at a time, the simplest API. httpx is a newer client with the same requests-like API but adds native async support (AsyncClient) and HTTP/2. aiohttp is async-only, built around its own event loop, and is also usable as a server framework.
Think of it as
requests is a phone call — you dial, you wait, you hang up, one at a time. httpx is that same phone call, but the same person can also send several messages at once and keep working while waiting for replies (async). aiohttp is a dedicated messaging app built for handling many conversations simultaneously from the start — it was never designed to make just one blocking call and wait.
What we're doing: Make the same GET request with all three libraries and confirm each one actually reaches httpbin.org and gets the same query parameters back.
- 6
- requests.get is a module-level function — no client object to construct for a single call.
- 9
- httpx.get mirrors requests' call shape exactly — params=, .status_code, .json() all match.
- 13
- aiohttp requires both an outer ClientSession and an inner response context manager, and every read (.json()) is awaited.
requests: 200 {'q': '1'}
httpx: 200 {'q': '1'}
aiohttp: (200, {'q': '1'})Why this works: All three libraries reach the same server and get the same echoed query parameters back, confirming the API differences are about call shape and concurrency model — not about what HTTP they can perform. requests and httpx share almost identical sync call shapes; aiohttp's two-level async context manager is the one genuinely different pattern to learn.
Picking aiohttp for a script that never needs concurrency
Wrong
Better
What you see: No functional bug — the async version works — but every single-request script now needs asyncio.run(), an async function, and a nested context manager to do what requests.get(url).json() does in one line.
Why: aiohttp and httpx.AsyncClient earn their complexity when many requests run concurrently — asyncio.gather() overlapping their wait time. A script making one request, or requests one after another with no concurrency benefit, pays async's ceremony cost for zero speed benefit.
- requests
- Synchronous only — every call blocks
- requests.get(url), response.json()
- Largest ecosystem of adapters/plugins
- httpx / aiohttp
- httpx: one API, both httpx.Client (sync) and httpx.AsyncClient (async)
- aiohttp: async-only, built on asyncio, also usable as a server
- Both needed for concurrent HTTP calls without threads
Choosing a client
Together
Remember: requests is sync-only with the biggest ecosystem; httpx gives one API for both sync and async plus HTTP/2; aiohttp is async-only and often doubles as a server.
See also: async http clients · building a resilient client · content negotiation and performance · choosing threading multiprocessing or asyncio

