Working with APIs in Python
APIs (Application Programming Interfaces) allow your Python programs to communicate with web services, databases, and other applications. Python's requests library makes HTTP API interaction simple and intuitive.
HTTP Basics
| Method | Purpose | Example |
|---|---|---|
| GET | Retrieve data | Fetch user profile |
| POST | Create data | Submit a form |
| PUT | Update data (full) | Replace user profile |
| PATCH | Update data (partial) | Update email only |
| DELETE | Remove data | Delete a post |
Making GET Requests
python
import requests
# Simple GET request
response = requests.get("https://jsonplaceholder.typicode.com/posts/1")
# Check status
print(response.status_code) # 200
print(response.ok) # True (status < 400)
# Get response data
data = response.json() # Parse JSON response
print(data["title"])
print(data["body"])
# Response attributes
print(response.headers["Content-Type"])
print(response.text) # Raw text response
print(response.url) # Final URL (after redirects)Query Parameters
python
# Pass parameters in URL
params = {
"userId": 1,
"completed": "true"
}
response = requests.get(
"https://jsonplaceholder.typicode.com/todos",
params=params
)
# URL becomes: .../todos?userId=1&completed=true
todos = response.json()
print(f"Found {len(todos)} completed todos for user 1")Making POST Requests
python
import requests
# POST with JSON data
new_post = {
"title": "My New Post",
"body": "This is the content",
"userId": 1
}
response = requests.post(
"https://jsonplaceholder.typicode.com/posts",
json=new_post # Automatically sets Content-Type
)
print(response.status_code) # 201 (Created)
created = response.json()
print(f"Created post with ID: {created['id']}")
# POST with form data
response = requests.post(
"https://httpbin.org/post",
data={"username": "alice", "password": "secret"}
)PUT, PATCH, and DELETE
python
import requests
BASE = "https://jsonplaceholder.typicode.com"
# PUT - full update
updated_post = {
"id": 1,
"title": "Updated Title",
"body": "Updated content",
"userId": 1
}
response = requests.put(f"{BASE}/posts/1", json=updated_post)
print(response.json()["title"]) # Updated Title
# PATCH - partial update
response = requests.patch(
f"{BASE}/posts/1",
json={"title": "Only Title Changed"}
)
print(response.json()["title"]) # Only Title Changed
# DELETE
response = requests.delete(f"{BASE}/posts/1")
print(response.status_code) # 200Headers and Authentication
python
import requests
# Custom headers
headers = {
"Authorization": "Bearer your-api-token-here",
"Accept": "application/json",
"User-Agent": "MyPythonApp/1.0"
}
response = requests.get(
"https://api.example.com/data",
headers=headers
)
# Basic authentication
response = requests.get(
"https://api.example.com/protected",
auth=("username", "password")
)
# API key in query parameters
response = requests.get(
"https://api.example.com/data",
params={"api_key": "your-key-here"}
)Error Handling
python
import requests
def fetch_data(url):
"""Fetch data with proper error handling."""
try:
response = requests.get(url, timeout=10)
# Raise exception for 4xx/5xx status codes
response.raise_for_status()
return response.json()
except requests.exceptions.ConnectionError:
print("Failed to connect to server")
except requests.exceptions.Timeout:
print("Request timed out")
except requests.exceptions.HTTPError as e:
print(f"HTTP error: {e.response.status_code}")
if e.response.status_code == 404:
print("Resource not found")
elif e.response.status_code == 401:
print("Authentication required")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
# Usage
data = fetch_data("https://jsonplaceholder.typicode.com/posts/1")
if data:
print(data["title"])Working with JSON
python
import json
import requests
# Parse JSON response
response = requests.get("https://jsonplaceholder.typicode.com/users")
users = response.json()
# Process the data
for user in users[:3]:
print(f"{user['name']} ({user['email']})")
print(f" Company: {user['company']['name']}")
print(f" City: {user['address']['city']}")
# Pretty print JSON
print(json.dumps(users[0], indent=2))
# Send nested JSON
payload = {
"user": {
"name": "Alice",
"preferences": {
"theme": "dark",
"notifications": True
}
}
}
response = requests.post("https://httpbin.org/post", json=payload)Sessions (Persistent Connections)
python
import requests
# Session persists cookies, headers, and connections
session = requests.Session()
# Set default headers for all requests
session.headers.update({
"Authorization": "Bearer my-token",
"Accept": "application/json"
})
# All requests use the session's settings
response1 = session.get("https://httpbin.org/get")
response2 = session.get("https://httpbin.org/headers")
# Session also persists cookies
session.get("https://httpbin.org/cookies/set/session_id/abc123")
response = session.get("https://httpbin.org/cookies")
print(response.json())
# {'cookies': {'session_id': 'abc123'}}
# Close session when done
session.close()
# Or use context manager
with requests.Session() as s:
s.headers["Authorization"] = "Bearer token"
data = s.get("https://httpbin.org/get").json()Retry with Backoff
python
import requests
import time
def fetch_with_retry(url, max_retries=3, backoff_factor=1):
"""Fetch URL with exponential backoff retry."""
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
wait = backoff_factor * (2 ** attempt)
print(f"Attempt {attempt + 1} failed: {e}")
print(f"Retrying in {wait}s...")
time.sleep(wait)Practical Example: API Client Class
python
"""
A reusable REST API client.
"""
import requests
class APIClient:
"""Generic REST API client with error handling."""
def __init__(self, base_url, api_key=None, timeout=30):
self.base_url = base_url.rstrip("/")
self.timeout = timeout
self.session = requests.Session()
if api_key:
self.session.headers["Authorization"] = f"Bearer {api_key}"
self.session.headers["Accept"] = "application/json"
def _request(self, method, endpoint, **kwargs):
"""Make an HTTP request with error handling."""
url = f"{self.base_url}/{endpoint.lstrip('/')}"
kwargs.setdefault("timeout", self.timeout)
try:
response = self.session.request(method, url, **kwargs)
response.raise_for_status()
return response.json() if response.text else None
except requests.exceptions.HTTPError as e:
print(f"API Error {e.response.status_code}: {e.response.text[:200]}")
raise
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
raise
def get(self, endpoint, params=None):
return self._request("GET", endpoint, params=params)
def post(self, endpoint, data=None):
return self._request("POST", endpoint, json=data)
def put(self, endpoint, data=None):
return self._request("PUT", endpoint, json=data)
def delete(self, endpoint):
return self._request("DELETE", endpoint)
def close(self):
self.session.close()
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
# Usage
with APIClient("https://jsonplaceholder.typicode.com") as api:
# Get all posts
posts = api.get("/posts", params={"userId": 1})
print(f"User 1 has {len(posts)} posts")
# Get specific post
post = api.get("/posts/1")
print(f"Title: {post['title']}")
# Create a new post
new_post = api.post("/posts", data={
"title": "New Post",
"body": "Content here",
"userId": 1
})
print(f"Created: ID {new_post['id']}")
# Delete a post
api.delete("/posts/1")
print("Post deleted")Summary
- Use Python's
requestslibrary for HTTP API interaction - HTTP methods:
GET(read),POST(create),PUT/PATCH(update),DELETE(remove) - Parse JSON responses with
response.json() - Always handle errors with try-except and
response.raise_for_status() - Set timeouts to prevent hanging requests
- Use Sessions for persistent connections, cookies, and shared headers
- Implement retry logic with exponential backoff for resilience
- Build API client classes for clean, reusable code
Next, we'll learn about database connectivity in Python.