The most widely used Python HTTP library. Simple, batteries-included. Also see httpx for async HTTP.
// GET
const res = await fetch("https://api.github.com/users/torvalds");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data.name);
// POST with JSON body
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice" }),
});
// With auth
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }
});
import requests
# GET — raises on non-2xx with .raise_for_status()
res = requests.get("https://api.github.com/users/torvalds")
res.raise_for_status() # raises HTTPError if 4xx/5xx
data = res.json()
print(data["name"])
# POST with JSON body
res = requests.post("/api/users", json={"name": "Alice"})
# With auth (Bearer token)
res = requests.get(url, headers={"Authorization": f"Bearer {token}"})
# Query params
res = requests.get("/search", params={"q": "python", "page": 2})
# → /search?q=python&page=2
# Install
pip install requests
res = requests.get("https://api.github.com/users/torvalds")
# Status
res.status_code # 200
res.ok # True if 200–299
res.raise_for_status() # raises requests.HTTPError if not ok
# Body
res.json() # parse JSON → dict/list
res.text # raw string body
res.content # raw bytes (for binary files)
# Headers
res.headers["Content-Type"] # "application/json; charset=utf-8"
res.headers.get("X-RateLimit-Remaining")
# URL after redirects
res.url # final URL
# Cookies
res.cookies["session_id"]
import requests
requests.get(url, params={...})
requests.post(url, json={...}) # JSON body
requests.post(url, data={...}) # form-encoded body
requests.post(url, files={"file": open("report.pdf", "rb")}) # multipart
requests.put(url, json={...})
requests.patch(url, json={...})
requests.delete(url)
requests.head(url)
requests.options(url)
A Session persists cookies, headers, and connection pooling across requests — like Axios instances in JS.
import requests
# Without session — creates new connection each time
for user_id in range(100):
requests.get(f"/api/users/{user_id}", headers={"Authorization": f"Bearer {token}"})
# With session — reuses connection, keeps auth header
with requests.Session() as session:
session.headers.update({
"Authorization": f"Bearer {token}",
"Accept": "application/json",
})
session.base_url = "https://api.example.com" # not built-in, just a pattern
for user_id in range(100):
res = session.get(f"https://api.example.com/users/{user_id}")
res.raise_for_status()
print(res.json()["name"])
import requests
from requests.exceptions import (
HTTPError, ConnectionError, Timeout, RequestException
)
def fetch_user(user_id: int) -> dict:
try:
res = requests.get(
f"https://api.example.com/users/{user_id}",
timeout=(3.05, 10) # (connect timeout, read timeout) in seconds
)
res.raise_for_status()
return res.json()
except Timeout:
raise RuntimeError(f"Request timed out for user {user_id}")
except HTTPError as e:
if e.response.status_code == 404:
return None
raise
except ConnectionError:
raise RuntimeError("No internet connection")
except RequestException as e:
raise RuntimeError(f"Request failed: {e}")
# Always set a timeout — never leave it as None (default = wait forever!)
By default, requests will wait forever if the server doesn't respond. Always pass timeout=. A tuple (connect, read) gives you fine-grained control.
Use httpx when you're in an async context (Lesson 10). The API is nearly identical to requests.
import httpx
import asyncio
async def fetch_users(user_ids: list[int]) -> list[dict]:
async with httpx.AsyncClient(
base_url="https://api.example.com",
headers={"Authorization": f"Bearer {token}"},
timeout=10.0,
) as client:
tasks = [client.get(f"/users/{uid}") for uid in user_ids]
responses = await asyncio.gather(*tasks)
return [r.raise_for_status().json() for r in responses]
# httpx also has a sync client — drop-in for requests
with httpx.Client(base_url="https://api.example.com") as client:
res = client.get("/users/1")
print(res.json())
Use requests for sync scripts. Use httpx when you need async HTTP or HTTP/2 support. Both have almost identical APIs so switching is trivial.
Validate and type API responses with Pydantic — like Zod for Python.
pip install pydantic
from pydantic import BaseModel, HttpUrl
import requests
class GitHubUser(BaseModel):
login: str
name: str | None = None
public_repos: int
followers: int
html_url: HttpUrl
def get_github_user(username: str) -> GitHubUser:
res = requests.get(f"https://api.github.com/users/{username}", timeout=5)
res.raise_for_status()
return GitHubUser(**res.json()) # validates and parses!
user = get_github_user("torvalds")
print(user.name) # "Linus Torvalds"
print(user.public_repos) # 7 (typed as int)
print(type(user.html_url)) # Url object, not raw string
1. What does res.raise_for_status() do?
2. How do you pass query parameters with requests.get()?
3. When should you use httpx instead of requests?
4. What is the danger of not setting a timeout in requests?
Questions answered correctly.