| name | httpx |
| description | Modern async HTTP client for Python with sync/async API, HTTP/2 support, connection pooling, retries, and timeouts |
| metadata | {"author":"mte90","version":"1.0.0","tags":["python","http","async","client","network"]} |
httpx
Modern HTTP client for Python.
Overview
httpx is a modern, full-featured HTTP client for Python that provides a simple but comprehensive API for making HTTP requests. It supports both synchronous and asynchronous programming, making it suitable for a wide variety of use cases.
Key Features:
- Sync and async APIs
- HTTP/2 support
- Connection pooling
- Timeouts and retries
- Cookie persistence
- Request/response streaming
- Proxies support
- Authentication
- Modern Python type hints
Installation
pip install httpx
pip install httpx[http2]
pip install httpx[socks]
pip install httpx[http2,socks,trio,curio]
Basic Usage
Synchronous Requests
import httpx
response = httpx.get("https://example.com")
print(response.status_code)
print(response.text)
print(response.json())
response = httpx.post(
"https://api.example.com/users",
json={"name": "John", "email": "john@example.com"}
)
print(response.status_code)
response = httpx.put(
"https://api.example.com/users/1",
data={"name": "Jane"}
)
response = httpx.delete("https://api.example.com/users/1")
response = httpx.head("https://example.com")
print(response.headers)
response = httpx.options("https://api.example.com")
print(response.headers["allow"])
Async Requests
import asyncio
import httpx
async def main():
async with httpx.AsyncClient() as client:
response = await client.get("https://example.com")
print(response.status_code)
print(response.text)
asyncio.run(main())
Response Handling
Status Codes
import httpx
response = httpx.get("https://example.com")
print(response.status_code)
print(response.is_success)
print(response.is_redirect)
print(response.is_client_error)
print(response.is_server_error)
response = httpx.get("https://example.com/not-found")
try:
response.raise_for_status()
except httpx.HTTPStatusError as e:
print(f"Error: {e.response.status_code}")
Response Content
import httpx
response = httpx.get("https://example.com")
print(response.text)
print(response.content)
data = response.json()
print(data["key"])
async with httpx.AsyncClient() as client:
async with client.stream("GET", "https://example.com/large-file") as response:
async for chunk in response.aiter_bytes():
print(chunk)
async for line in response.aiter_text():
print(line)
Headers
import httpx
response = httpx.get("https://example.com")
print(response.headers)
print(response.headers["content-type"])
print(response.headers.get("content-length"))
response = httpx.get(
"https://api.example.com",
headers={
"Authorization": "Bearer token",
"Accept": "application/json",
"User-Agent": "MyApp/1.0"
}
)
Cookies
import httpx
response = httpx.get("https://example.com")
print(response.cookies)
print(response.cookies["session_id"])
response = httpx.get(
"https://example.com",
cookies={"session_id": "abc123"}
)
import httpx
cookies = httpx.Cookies()
cookies.set("session", "value", domain="example.com")
response = httpx.get("https://example.com", cookies=cookies)
Request Configuration
Query Parameters
import httpx
response = httpx.get(
"https://api.example.com/search",
params={"query": "python", "page": 1}
)
response = httpx.get(
"https://api.example.com/users",
params={"id": [1, 2, 3]}
)
Request Body
import httpx
response = httpx.post(
"https://api.example.com/users",
json={"name": "John", "age": 30}
)
response = httpx.post(
"https://api.example.com/login",
data={"username": "john", "password": "secret"}
)
response = httpx.post(
"https://api.example.com/upload",
files={"document": open("file.pdf", "rb")}
)
response = httpx.post(
"https://api.example.com/upload",
data={"title": "My Document"},
files={"document": ("doc.pdf", open("file.pdf", "rb"), "application/pdf")}
)
response = httpx.post(
"https://api.example.com/data",
content=b"raw bytes"
)
Authentication
import httpx
from httpx import Auth
response = httpx.get(
"https://api.example.com/protected",
auth=("username", "password")
)
class CustomAuth(Auth):
def __init__(self, token):
self.token = token
def auth_flow(self, request):
request.headers["Authorization"] = f"Bearer {self.token}"
yield request
response = httpx.get(
"https://api.example.com",
auth=CustomAuth("my-token")
)
from httpx import DigestAuth
response = httpx.get(
"https://api.example.com",
auth=DigestAuth("username", "password")
)
Timeouts
import httpx
from httpx import Timeout
response = httpx.get("https://example.com")
response = httpx.get(
"https://example.com",
timeout=10.0
)
timeout = Timeout(
connect=5.0,
read=30.0,
write=10.0,
pool=5.0
)
response = httpx.get("https://example.com", timeout=timeout)
response = httpx.get(
"https://example.com",
timeout=None
)
SSL Verification
import httpx
response = httpx.get("https://example.com")
response = httpx.get(
"https://example.com",
verify=False
)
response = httpx.get(
"https://example.com",
verify="/path/to/ca-bundle.crt"
)
response = httpx.get(
"https://example.com",
cert=("/path/to/client.crt", "/path/to/client.key")
)
Async Client
AsyncClient Setup
import asyncio
import httpx
async def fetch():
async with httpx.AsyncClient() as client:
response = await client.get("https://example.com")
return response.text
asyncio.run(fetch())
client = httpx.AsyncClient(
base_url="https://api.example.com",
headers={"Authorization": "Bearer token"},
timeout=30.0
)
async def main():
response = await client.get("/users/1")
print(response.json())
asyncio.run(main())
Concurrent Requests
import asyncio
import httpx
async def fetch_all():
urls = [
"https://api.example.com/users/1",
"https://api.example.com/users/2",
"https://api.example.com/users/3",
]
async with httpx.AsyncClient() as client:
responses = await asyncio.gather(
*[client.get(url) for url in urls]
)
for response in responses:
print(response.json())
asyncio.run(fetch_all())
async def fetch_with_limits():
limits = httpx.Limits(
max_keepalive_connections=20,
max_connections=100,
keepalive_expiry=30
)
async with httpx.AsyncClient(limits=limits) as client:
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
asyncio.run(fetch_with_limits())
Streaming
import asyncio
import httpx
async def stream_download():
async with httpx.AsyncClient() as client:
async with client.stream(
"GET",
"https://example.com/large-file"
) as response:
async for chunk in response.aiter_bytes(chunk_size=8192):
print(f"Received {len(chunk)} bytes")
asyncio.run(stream_download())
async def stream_upload():
async with httpx.AsyncClient() as client:
async def generate():
for i in range(10):
yield f"chunk {i}\n".encode()
await client.post(
"https://api.example.com/upload",
content=generate()
)
asyncio.run(stream_upload())
Advanced Features
HTTP/2 Support
import httpx
client = httpx.Client(
http2=True
)
async with httpx.AsyncClient(http2=True) as client:
responses = await asyncio.gather(
client.get("https://example.com/page1"),
client.get("https://example.com/page2"),
client.get("https://example.com/page3"),
)
Connection Pooling
import httpx
limits = httpx.Limits(
max_keepalive_connections=20,
max_connections=100,
keepalive_expiry=30
)
client = httpx.Client(limits=limits)
async_client = httpx.AsyncClient(limits=limits)
with httpx.Client(limits=limits) as client:
response = client.get("https://example.com")
Retries
import httpx
from httpx import Retry
retry = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[500, 502, 503, 504],
allow_redirects=False,
)
client = httpx.Client(mounts={
"http://": httpx.HTTPTransport(retries=retry),
"https://": httpx.HTTPTransport(retries=retry),
})
import httpx_retry
client = httpx_retry.RetryClient(
total=3,
backoff_factor=0.5,
retry_on_status=[500, 502, 503, 504]
)
Proxies
import httpx
response = httpx.get(
"https://example.com",
proxies={"http://": "http://proxy:8080"}
)
response = httpx.get(
"https://example.com",
proxies={"https://": "http://proxy:8080"}
)
response = httpx.get(
"https://example.com",
proxies={"http://": "socks5://user:pass@proxy:1080"}
)
response = httpx.get(
"https://api.example.com",
proxies={
"http://api.example.com": "http://api-proxy:8080",
"http://": "http://default-proxy:8080"
}
)
Event Hooks
import httpx
def log_request(request):
print(f"Request: {request.method} {request.url}")
def log_response(response):
print(f"Response: {response.status_code}")
client = httpx.Client(
event_hooks={
"request": [log_request],
"response": [log_response],
}
)
response = client.get("https://example.com")
async def async_log_request(request):
print(f"Request: {request.method}")
async with httpx.AsyncClient(
event_hooks={
"request": [async_log_request],
}
) as client:
response = await client.get("https://example.com")
Testing
Mocking with httpx-mock
import httpx
import pytest
from httpx import MockTransport
def test_simple_mock():
"""Simple mock without external requests."""
transport = MockTransport(lambda request: httpx.Response(200, json={"key": "value"}))
with httpx.Client(transport=transport) as client:
response = client.get("https://example.com/api")
assert response.status_code == 200
assert response.json() == {"key": "value"}
def test_mock_status():
"""Mock error responses."""
transport = MockTransport(lambda request: httpx.Response(404))
with httpx.Client(transport=transport) as client:
response = client.get("https://example.com/not-found")
assert response.status_code == 404
def test_mock_redirect():
"""Mock redirects."""
transport = MockTransport(
lambda request: (
httpx.Response(301, headers={"location": "https://example.com/new"})
if request.url.path == "/old"
else httpx.Response(200)
)
)
with httpx.Client(transport=transport, follow_redirects=True) as client:
response = client.get("https://example.com/old")
assert response.status_code == 200
Using respx
import httpx
import respx
import pytest
@respx.mock
def test_with_respx():
"""Mock with respx library."""
route = respx.get("https://example.com/api").mock(
return_value=httpx.Response(200, json={"data": "test"})
)
response = httpx.get("https://example.com/api")
assert response.json() == {"data": "test"}
assert route.called
assert route.call_count == 1
@respx.mock
def test_mock_side_effect():
"""Mock with side effects."""
call_count = 0
def side_effect(request):
nonlocal call_count
call_count += 1
return httpx.Response(200, json={"count": call_count})
route = respx.get("https://example.com/counter").mock(side_effect=side_effect)
r1 = httpx.get("https://example.com/counter")
assert r1.json() == {"count": 1}
r2 = httpx.get("https://example.com/counter")
assert r2.json() == {"count": 2}
Async Testing
import pytest
import httpx
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_async_mock():
"""Test async client with mock."""
transport = AsyncMock()
transport.handle_async_request = AsyncMock(
return_value=httpx.Response(200, json={"async": True})
)
async with httpx.AsyncClient(transport=transport) as client:
response = await client.get("https://example.com/api")
assert response.json() == {"async": True}
@pytest.mark.asyncio
async def test_patch_async():
"""Test with patch."""
with patch("httpx.AsyncClient") as mock_client:
mock_response = httpx.Response(200, text="mocked")
mock_client.return_value.__aenter__.return_value.get = AsyncMock(
return_value=mock_response
)
async with httpx.AsyncClient() as client:
response = await client.get("https://example.com")
assert response.text == "mocked"
Best Practices
1. Use Context Managers
with httpx.Client() as client:
response = client.get("https://api.example.com/users")
users = response.json()
client = httpx.Client()
try:
response = client.get("https://api.example.com/users")
finally:
client.close()
2. Reuse Client for Performance
client = httpx.Client()
try:
for user_id in range(1, 100):
response = client.get(f"https://api.example.com/users/{user_id}")
process(response.json())
finally:
client.close()
for user_id in range(1, 100):
with httpx.Client() as client:
response = client.get(f"https://api.example.com/users/{user_id}")
3. Always Set Timeouts
timeout = httpx.Timeout(10.0, connect=5.0)
response = client.get("https://api.example.com", timeout=timeout)
response = client.get("https://api.example.com")
4. Handle Exceptions
import httpx
from httpx import ConnectTimeout, ReadTimeout, HTTPError
try:
response = client.get("https://api.example.com")
except ConnectTimeout:
print("Connection timed out")
except ReadTimeout:
print("Read timed out")
except httpx.HTTPError as e:
print(f"HTTP error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
5. Use Async for I/O-bound Tasks
async def fetch_all(urls):
async with httpx.AsyncClient() as client:
return await asyncio.gather(
*[client.get(url) for url in urls]
)
def fetch_all_sync(urls):
with httpx.Client() as client:
return [client.get(url).json() for url in urls]
Integration Examples
FastAPI
import httpx
from fastapi import FastAPI, Depends
app = FastAPI()
def get_http_client():
with httpx.Client() as client:
yield client
@app.get("/users/{user_id}")
async def get_user(
user_id: int,
client: httpx.Client = Depends(get_http_client)
):
response = client.get(f"https://api.example.com/users/{user_id}")
return response.json()
@app.get("/posts/{post_id}")
async def get_post(post_id: int):
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/posts/{post_id}")
return response.json()
Django
import httpx
from django.http import JsonResponse
def external_api_view(request):
with httpx.Client() as client:
response = client.get(
"https://api.example.com/data",
headers={"Authorization": f"Bearer {request.user.token}"}
)
return JsonResponse(response.json())
def api_with_timeout(request):
with httpx.Client(timeout=30.0) as client:
response = client.get("https://api.example.com/data")
return JsonResponse(response.json())
Common Issues
SSL Certificate Errors
response = client.get("https://example.com", verify=False)
response = client.get(
"https://example.com",
verify="/path/to/ca-bundle.crt"
)
Connection Pool Exhaustion
limits = httpx.Limits(
max_connections=50,
max_keepalive_connections=20
)
client = httpx.Client(limits=limits)
Timeout Issues
timeout = httpx.Timeout(10.0, connect=5.0)
response = client.get("https://api.example.com", timeout=timeout)
response = client.get(
"https://api.example.com/long-operation",
timeout=None
)
References