Python httpx

Modern HTTP client with async support, connection pooling, and HTTP/2.

Basic Requests

GET, POST, PUT, DELETE — httpx mirrors requests API but adds async support
import httpx

# GET with query parameters
response = httpx.get(
    "https://api.example.com/devices",
    params={"vlan": 10, "limit": 50},
)

# POST with JSON body
response = httpx.post(
    "https://api.example.com/devices",
    json={"hostname": "switch-01", "ip": "10.50.1.100", "vlan": 10},
)

# PUT and DELETE
response = httpx.put("https://api.example.com/devices/1", json={"vlan": 20})
response = httpx.delete("https://api.example.com/devices/1")

Response Handling

Status code, JSON parsing, and raise_for_status() for error propagation
import httpx

response = httpx.get("https://api.example.com/devices/1")

response.status_code        # 200
response.json()             # parsed dict
response.text               # raw string body
response.headers            # case-insensitive dict
response.headers["content-type"]  # "application/json"

# Raise httpx.HTTPStatusError for 4xx/5xx -- do this instead of manual status checks
response.raise_for_status()

# Check without raising
response.is_success         # True for 2xx
response.is_redirect        # True for 3xx
response.is_client_error    # True for 4xx
response.is_server_error    # True for 5xx

Headers and Authentication

Custom headers, bearer tokens, and basic auth
import httpx

# Custom headers
response = httpx.get(
    "https://api.example.com/secure",
    headers={"X-API-Key": api_key, "Accept": "application/json"},
)

# Bearer token auth
response = httpx.get(
    "https://api.example.com/secure",
    headers={"Authorization": f"Bearer {token}"},
)

# Basic auth -- httpx handles encoding
response = httpx.get(
    "https://api.example.com/secure",
    auth=("username", "password"),
)

Timeouts

Always set timeouts — httpx defaults to 5s, but be explicit in production
import httpx

# Single timeout for all phases
response = httpx.get("https://api.example.com/slow", timeout=30.0)

# Granular timeouts -- connect fast, read slow (large responses)
timeout = httpx.Timeout(
    connect=5.0,
    read=30.0,
    write=10.0,
    pool=5.0,
)
response = httpx.get("https://api.example.com/report", timeout=timeout)

Synchronous Client (Session)

Client reuses TCP connections — faster for multiple requests to same host
import httpx

with httpx.Client(
    base_url="https://api.example.com",
    headers={"Authorization": f"Bearer {token}"},
    timeout=10.0,
) as client:
    devices = client.get("/devices").json()
    for device in devices:
        detail = client.get(f"/devices/{device['id']}").json()
        print(detail["hostname"])

Async Client

AsyncClient for FastAPI, background tasks, and concurrent requests
import asyncio
import httpx

async def fetch_all_devices():
    async with httpx.AsyncClient(
        base_url="https://api.example.com",
        headers={"Authorization": f"Bearer {token}"},
    ) as client:
        # Sequential
        response = await client.get("/devices")
        devices = response.json()

        # Concurrent -- fan out requests
        tasks = [client.get(f"/devices/{d['id']}") for d in devices]
        responses = await asyncio.gather(*tasks)
        return [r.json() for r in responses]

Streaming Responses

Stream large responses without loading entire body into memory
import httpx

with httpx.stream("GET", "https://example.com/large-export.csv") as response:
    response.raise_for_status()
    with open("export.csv", "wb") as f:
        for chunk in response.iter_bytes(chunk_size=8192):
            f.write(chunk)

# Async streaming
async with httpx.AsyncClient() as client:
    async with client.stream("GET", url) as response:
        async for line in response.aiter_lines():
            process(line)

File Upload

Multipart file upload — single and multiple files
import httpx
from pathlib import Path

# Single file
files = {"file": ("config.txt", Path("config.txt").read_bytes(), "text/plain")}
response = httpx.post("https://api.example.com/upload", files=files)

# File with additional form fields
response = httpx.post(
    "https://api.example.com/upload",
    files={"file": ("backup.tar.gz", open("backup.tar.gz", "rb"))},
    data={"description": "Daily ISE backup", "category": "backup"},
)

Error Handling Pattern

Structured error handling — distinguish network errors from HTTP errors
import httpx

try:
    response = httpx.get("https://api.example.com/devices", timeout=10.0)
    response.raise_for_status()
    data = response.json()
except httpx.ConnectError:
    print("Cannot reach API -- check network/DNS")
except httpx.TimeoutException:
    print("Request timed out -- API may be overloaded")
except httpx.HTTPStatusError as e:
    print(f"HTTP {e.response.status_code}: {e.response.text}")
except httpx.RequestError as e:
    print(f"Request failed: {e}")