requests: Talking to the Internet
Every weather app, chatbot, and price tracker has the same heartbeat: ask a server, get data back. The requests library is Python's cleanest way to do it — and this lesson builds your first real API integration.
The anatomy of an API call
import requests
response = requests.get("https://api.github.com/users/GalvanMoto", timeout=10)
print(response.status_code) # 200 — means "success"Three parts:
JSON APIs — the standard response
Modern APIs return JSON — which, as you know from Module 8, maps perfectly to Python dicts. requests parses it for you:
import requests
response = requests.get("https://api.github.com/users/GalvanMoto", timeout=10)
data = response.json() # JSON → Python dict, automatically
print(data["name"]) # the account's display name
print(data["public_repos"]) # repository count
print(list(data.keys())[:5]) # peek at what's availableresponse.json() is your bridge: internet → dict → your code. Everything you learned about dictionaries applies directly.
A real app: the GitHub lookup
import requests
username = input("GitHub username: ")
url = f"https://api.github.com/users/{username}"
response = requests.get(url, timeout=10)
if response.status_code == 200:
data = response.json()
print(f"Name: {data.get('name', 'N/A')}")
print(f"Repos: {data['public_repos']}")
print(f"Followers: {data['followers']}")
elif response.status_code == 404:
print("No such user")
else:
print(f"GitHub problem: {status_code}")Notice the defensive shape: status check first, then parse. Parsing before checking crashes on 404s (the "user" JSON won't exist). This guard-first pattern is non-negotiable in real code.
Sending parameters — the params argument
Many APIs take query parameters (the ?key=value parts of URLs). Build them properly with params=:
import requests
# Instead of fragile string-building:
# requests.get("https://api.openweathermap.org/data/2.5/weather?q=Delhi&appid=KEY")
response = requests.get(
"https://api.openweathermap.org/data/2.5/weather",
params={"q": "Delhi", "appid": "YOUR_KEY", "units": "metric"},
timeout=10,
)
data = response.json()
print(f"{data['name']}: {data['main']['temp']}°C")params= URL-encodes everything correctly — spaces, symbols, unicode — which manual string-building gets wrong. This is exactly the pattern from the weather app tutorial.
Headers — identifying yourself and authenticating
Two header uses you'll meet immediately:
# 1. A User-Agent (many APIs reject default python requests)
headers = {"User-Agent": "MyLearningApp/1.0"}
# 2. Authentication (token-based APIs — like Mistral AI)
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.get(url, headers=headers, timeout=10)The Bearer-token pattern is how every AI API in this site's projects authenticates — the key travels in the header, never in the URL.
The error-handling shape (production pattern)
import requests
def fetch_json(url, params=None):
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status() # raises for 4xx/5xx!
return response.json()
except requests.exceptions.Timeout:
print("Server took too long")
except requests.exceptions.HTTPError as err:
print(f"HTTP error: {err}")
except requests.exceptions.RequestException as err:
print(f"Network problem: {err}")
return None
data = fetch_json("https://api.github.com/users/GalvanMoto")
if data:
print(data.get("name"))Three new tools: raise_for_status() converts bad status codes into exceptions (one check covers all 4xx/5xx), RequestException is the parent of ALL requests errors (catch it last), and returning None on failure lets the caller decide. This shape — try, raise_for_status, specific excepts, return None — is production-grade from day one.
Common Errors & Fixes
✅ Checkpoint
params= instead of building the URL string? *(Correct encoding of spaces and symbols)*raise_for_status() do? *(Raises an exception for 4xx/5xx — one check covers all bad statuses)*Next: 🧪 Practice — a complete project setup from empty folder to working API call.