HTTP in Python — requests & httpx
debt(d5/e1/b3/t5)
Closest to 'specialist tool catches' (d5) — semgrep/ruff rules can flag requests.get() without timeout, but default linters won't catch missing raise_for_status() or Session reuse issues; many problems only surface in production under slow upstream conditions.
Closest to 'one-line patch' (e1) — quick_fix is adding a timeout=N parameter or swapping requests.get for httpx.get; per-call fixes are trivial single-line edits.
Closest to 'localised tax' (b3) — HTTP client choice applies to web/cli contexts and tends to be used across many call sites, but switching libraries (requests↔httpx) is mechanical since APIs are similar; doesn't shape system architecture.
Closest to 'notable trap' (t5) — misconception that urllib≈requests and that requests has sensible defaults; the no-default-timeout behavior is a well-documented gotcha that devs typically learn only after a production hang.
Also Known As
TL;DR
Explanation
requests: synchronous HTTP with a clean API — Session for connection pooling, automatic JSON encoding/decoding, timeout parameter (always set it), retry with HTTPAdapter. httpx: requests-compatible API plus async (async with httpx.AsyncClient()), HTTP/2 support, better streaming. Key practices: always set timeout, use sessions for multiple requests to the same host (connection reuse), check response.raise_for_status() rather than manual status code checking, and never disable SSL verification (verify=False is insecure).
Common Misconception
Why It Matters
Common Mistakes
- No timeout parameter — process hangs indefinitely on slow servers.
- Not using Session for multiple requests — creates a new TCP connection per request.
- verify=False to bypass SSL — disables certificate verification, enabling MITM.
- Not calling raise_for_status() — 400/500 responses silently succeed without it.
Code Examples
import requests
# No timeout — hangs forever if server is slow:
response = requests.get('https://api.example.com/data')
# No error check — 500 treated as success:
data = response.json()
# New connection every request:
for url in urls:
requests.get(url) # New TCP connection each time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# Session with retry and timeout:
session = requests.Session()
retry = Retry(total=3, backoff_factor=0.3, status_forcelist=[500, 502, 503])
session.mount('https://', HTTPAdapter(max_retries=retry))
response = session.get('https://api.example.com/data', timeout=(3.05, 27))
response.raise_for_status() # Raises for 4xx/5xx
data = response.json()
# Async with httpx:
async with httpx.AsyncClient(timeout=30) as client:
response = await client.get(url)
response.raise_for_status()