Advanced Topics

Working with APIs in Python

Learn how to consume REST APIs in Python using the requests library, handle JSON data, authentication, and build API clients.

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

MethodPurposeExample
GETRetrieve dataFetch user profile
POSTCreate dataSubmit a form
PUTUpdate data (full)Replace user profile
PATCHUpdate data (partial)Update email only
DELETERemove dataDelete 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)  # 200

Headers 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 requests library 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.