The Python requests library is often called “HTTP for Humans” - and for good reason! It makes interacting with web services incredibly straightforward. Today, we’ll explore how to use this powerful library to interact with web APIs.

Making a Simple GET Request

Let’s start with a basic example that fetches data from the JSONPlaceholder API, a free fake API for testing and prototyping:

import requests

# Make a GET request to fetch a user
response = requests.get('https://jsonplaceholder.typicode.com/users/1')

# Check if the request was successful
if response.status_code == 200:
    # Parse the JSON response
    user_data = response.json()
    print(f"User: {user_data['name']}")
    print(f"Email: {user_data['email']}")
    print(f"Company: {user_data['company']['name']}")
else:
    print(f"Error: {response.status_code}")

Posting Data to an API

Here’s how you can make a POST request to create a new resource:

import requests
import json

# Data to send in the POST request
new_post = {
    'title': 'My New Blog Post',
    'body': 'This is the content of my post.',
    'userId': 1
}

# Make a POST request
response = requests.post(
    'https://jsonplaceholder.typicode.com/posts',
    json=new_post,  # requests will automatically JSON-encode this dict
    headers={
        'Content-Type': 'application/json',
    }
)

if response.status_code == 201:  # 201 means "Created"
    created_post = response.json()
    print("Post created successfully!")
    print(f"Post ID: {created_post['id']}")
    print(f"Title: {created_post['title']}")
else:
    print(f"Error: {response.status_code}")

Error Handling Best Practices

Here’s a more robust example that includes proper error handling:

import requests
from requests.exceptions import RequestException

def fetch_data(url: str) -> dict | None:
    """Fetches JSON data from a specified URL using HTTP GET request.

    Args:
        url (str): The URL endpoint to fetch data from.

    Returns:
        dict: The JSON response data if successful.
        None: If any error occurs during the request.

    Raises:
        requests.exceptions.HTTPError: If the HTTP response indicates an error (4xx, 5xx).
        requests.exceptions.ConnectionError: If connection to the server fails.
        requests.exceptions.Timeout: If the request exceeds 5 second timeout.
        requests.exceptions.RequestException: For other request-related errors.
    
    Note:
        All exceptions are caught internally and logged to stdout. The function
        will return None instead of raising exceptions.
    """
    try:
        response = requests.get(url, timeout=5)  # Add timeout of 5 seconds
        response.raise_for_status()  # Raises an HTTPError for bad responses (4xx, 5xx)
        return response.json()
    
    except requests.exceptions.HTTPError as http_err:
        print(f"HTTP error occurred: {http_err}")
    except requests.exceptions.ConnectionError as conn_err:
        print(f"Error connecting to the server: {conn_err}")
    except requests.exceptions.Timeout as timeout_err:
        print(f"Timeout error: {timeout_err}")
    except requests.exceptions.RequestException as err:
        print(f"An error occurred: {err}")
    return None

# Example usage
data = fetch_data('https://jsonplaceholder.typicode.com/users/1')
if data:
    print(f"Successfully fetched data for user: {data['name']}")

Why This Matters

Using the requests library properly can help you:

  • Handle API interactions robustly
  • Deal with errors gracefully
  • Create maintainable code
  • Save time compared to using lower-level libraries

Remember to always:

  1. Check status codes
  2. Handle exceptions appropriately
  3. Set timeouts for your requests
  4. Use proper headers when needed

The examples above demonstrate these best practices while keeping the code clean and readable.