Day 19: Working with APIs & JSON Handling
π Day 19: Working with APIs & JSON Handling
1. Learning Objectives
By the end of Day 19, you will be able to:
- Understand what APIs are and how they enable programs to communicate over the web
- Make HTTP requests using Python's
requestslibrary (GET, POST, PUT, DELETE) - Parse JSON responses and convert them into Python dictionaries
- Handle query parameters, headers, and authentication
- Manage API errors gracefully with status codes and exception handling
- Build a simple API client that fetches and processes real-world data
2. Concept Explanation
2.1 What is an API? — Programs Talking to Programs
API stands for Application Programming Interface. It's a set of rules that allows one piece of software to talk to another. A REST API (the most common type on the web) lets you send HTTP requests to a URL and receive data back — usually in JSON format.
Think of an API like a waiter at a restaurant:
| You (Client) | Waiter (API) | Kitchen (Server) |
|---|---|---|
| Order food | Takes order, delivers it | Cooks the food |
| You don't need to know how the kitchen works — you just get your meal. |
Similarly, you don't need to know how Twitter's database works — you just ask the API for tweets, and it returns them in a clean format.
2.2 HTTP Requests — The Language of the Web
Every time you visit a website, your browser makes an HTTP request. APIs use the same protocol:
| HTTP Method | Purpose | Example |
|---|---|---|
| GET | Retrieve data | Get weather for a city |
| POST | Send new data | Create a new user account |
| PUT | Update existing data | Edit a blog post |
| DELETE | Remove data | Delete a comment |
URL structure for APIs:
https://api.example.com/v1/users?page=2&limit=10
└──────┬──────┘ └─┬─┘ └──┬──┘ └──────┬────────┘
Base URL Version Endpoint Query Parameters
2.3 Installing and Using requests
The requests library is the standard for HTTP in Python. It's not built-in, so install it first:
pip install requests
First request — a simple GET:
import requests
response = requests.get("https://api.github.com")
print(response.status_code) # 200 means success
print(response.text[:200]) # First 200 characters of the response body
The response object has everything you need:
| Attribute / Method | What It Returns |
|---|---|
response.status_code | HTTP status code (200, 404, 500, etc.) |
response.text | Response body as a string |
response.json() | Parsed JSON as a Python dict/list |
response.headers | Response headers as a dict |
response.ok | True if status code is 200–399 |
response.raise_for_status() | Raises an exception if the request failed |
2.4 JSON — The Language of APIs
JSON (JavaScript Object Notation) is the universal data format for APIs. It looks almost identical to Python dictionaries and lists:
{
"name": "Alice",
"age": 28,
"skills": ["Python", "JavaScript"],
"address": {
"city": "Manila",
"country": "Philippines"
}
}
Python ↔ JSON conversion:
import json
# Python dict → JSON string (serialisation)
data = {"name": "Alice", "age": 28}
json_string = json.dumps(data, indent=2)
print(json_string)
# JSON string → Python dict (deserialisation)
python_dict = json.loads(json_string)
print(python_dict["name"]) # Alice
With requests, you rarely need json.loads() directly — just use .json():
response = requests.get("https://api.github.com/users/octocat")
user_data = response.json() # Already a Python dict!
print(user_data["login"]) # octocat
print(user_data["public_repos"]) # 8
2.5 Working with a Real API — Practice with JSONPlaceholder
JSONPlaceholder is a free fake API for testing. Let's explore it:
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
# GET all posts
response = requests.get(f"{BASE_URL}/posts")
posts = response.json()
print(f"Fetched {len(posts)} posts")
print(f"First post title: {posts[0]['title']}")
# GET a single post by ID
response = requests.get(f"{BASE_URL}/posts/1")
post = response.json()
print(f"Post 1: {post['title']}")
# POST — create a new post
new_post = {"title": "Learning Python", "body": "APIs are fun!", "userId": 1}
response = requests.post(f"{BASE_URL}/posts", json=new_post)
created = response.json()
print(f"Created post ID: {created['id']}")
# Query parameters — filter posts by user
response = requests.get(f"{BASE_URL}/posts", params={"userId": 1})
user1_posts = response.json()
print(f"User 1 has {len(user1_posts)} posts")
2.6 Handling Headers and Authentication
Many APIs require authentication. Common methods:
API Key in query parameter:
response = requests.get(
"https://api.weatherapi.com/v1/current.json",
params={"key": "YOUR_API_KEY", "q": "Manila"}
)
API Key in header:
headers = {"Authorization": "Bearer YOUR_TOKEN_HERE"}
response = requests.get("https://api.example.com/data", headers=headers)
Basic authentication:
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://api.example.com/secure",
auth=HTTPBasicAuth("username", "password")
)
2.7 Error Handling for API Requests
Network calls can fail for many reasons. Always handle them:
import requests
def fetch_data(url):
"""Safely fetch data from an API."""
try:
response = requests.get(url, timeout=10)
response.raise_for_status() # Raises HTTPError for 4xx/5xx
return response.json()
except requests.exceptions.Timeout:
print("Request timed out. Check your network.")
except requests.exceptions.ConnectionError:
print("Could not connect to the server.")
except requests.exceptions.HTTPError as e:
print(f"HTTP error: {e}")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
data = fetch_data("https://jsonplaceholder.typicode.com/posts")
if data:
print(f"Got {len(data)} items")
Common HTTP status codes:
| Code | Meaning |
|---|---|
| 200 | OK — request succeeded |
| 201 | Created — new resource made |
| 204 | No Content — success, nothing to return |
| 400 | Bad Request — your fault |
| 401 | Unauthorized — log in first |
| 403 | Forbidden — you don't have permission |
| 404 | Not Found — resource doesn't exist |
| 500 | Internal Server Error — their fault |
| 503 | Service Unavailable — try again later |
2.8 Building Reusable API Clients
Wrap API calls in a class for clean, maintainable code:
class JSONPlaceholderClient:
"""A reusable client for the JSONPlaceholder API."""
def __init__(self):
self.base_url = "https://jsonplaceholder.typicode.com"
def get_posts(self, user_id=None):
"""Get all posts, optionally filtered by user_id."""
params = {"userId": user_id} if user_id else {}
response = requests.get(f"{self.base_url}/posts", params=params)
response.raise_for_status()
return response.json()
def get_post(self, post_id):
"""Get a single post by ID."""
response = requests.get(f"{self.base_url}/posts/{post_id}")
response.raise_for_status()
return response.json()
def create_post(self, title, body, user_id=1):
"""Create a new post."""
data = {"title": title, "body": body, "userId": user_id}
response = requests.post(f"{self.base_url}/posts", json=data)
response.raise_for_status()
return response.json()
# Usage
client = JSONPlaceholderClient()
posts = client.get_posts(user_id=1)
print(f"User 1 posts: {len(posts)}")
3. Code Examples
Example 1: Weather Fetcher (Simulated)
import requests
def get_weather(city):
"""Fetch weather for a city using a free API.
Note: You'll need a free API key from https://openweathermap.org/api
This example uses a simulated response if no key is available.
"""
API_KEY = "YOUR_API_KEY" # Replace with a real key
url = "https://api.openweathermap.org/data/2.5/weather"
params = {"q": city, "appid": API_KEY, "units": "metric"}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
temp = data["main"]["temp"]
desc = data["weather"][0]["description"]
return f"{city}: {temp}°C, {desc}"
except requests.exceptions.RequestException as e:
return f"Weather fetch failed: {e}"
# print(get_weather("Manila"))
Example 2: GitHub User Info
import requests
def get_github_user(username):
"""Fetch public information about a GitHub user."""
url = f"https://api.github.com/users/{username}"
response = requests.get(url, timeout=10)
response.raise_for_status()
user = response.json()
return {
"name": user.get("name", "N/A"),
"repos": user["public_repos"],
"followers": user["followers"],
"avatar": user["avatar_url"],
}
info = get_github_user("octocat")
print(f"Name: {info['name']}")
print(f"Public repos: {info['repos']}")
print(f"Followers: {info['followers']}")
Example 3: POST with Error Handling
import requests
def create_comment(post_id, name, email, body):
"""Post a comment to JSONPlaceholder."""
url = f"https://jsonplaceholder.typicode.com/posts/{post_id}/comments"
data = {"name": name, "email": email, "body": body}
try:
response = requests.post(url, json=data, timeout=10)
response.raise_for_status()
created = response.json()
print(f"Comment created with ID: {created['id']}")
return created
except requests.exceptions.HTTPError as e:
print(f"Server rejected comment: {e}")
except requests.exceptions.RequestException as e:
print(f"Network error: {e}")
return None
create_comment(1, "Alice", "alice@example.com", "Great post!")
4. Hands-On Exercises
Exercise 1: Fetch and Display
Using the JSONPlaceholder API (/posts), fetch all posts and display each post's title and body (first 80 characters only). Format the output nicely.
Exercise 2: Filter by User
Ask the user for a user ID (1–10). Fetch all posts by that user from JSONPlaceholder. Print the number of posts and list their titles.
Exercise 3: JSON File Merger
Create a Python script that:
- Fetches posts from JSONPlaceholder
- Saves them to
posts.json - Then reads
posts.jsonback into Python and prints the number of posts loaded
Exercise 4: Error-Resistant API Caller
Write a function safe_get(url) that takes any URL, makes a GET request, and returns the JSON. Handle Timeout, ConnectionError, and HTTPError. Return None on any failure.
Exercise 5: Headers Inspector
Make a GET request to https://httpbin.org/headers with a custom header X-Learning: Python. Print the response JSON to see what the server received. Verify your custom header was sent.
5. Applied Challenge Task π️
GitHub Profile Analyzer CLI
Build a command-line tool that analyzes a GitHub user's public profile using the GitHub API.
Requirements:
Ask for a GitHub username at the command line.
Fetch the user's profile from
https://api.github.com/users/{username}.Fetch their public repositories from
https://api.github.com/users/{username}/repos.Display a profile summary:
=== GITHUB PROFILE: octocat === Name: The Octocat Bio: (if available) Public Repos: 8 Followers: 9876 Following: 2 ==============================Display repository statistics:
- Total number of repos
- Most starred repo (name + stars)
- Most used language (count them up)
- Average repo size
Error handling:
- If the user doesn't exist, show a friendly message.
- If rate-limited (GitHub allows 60 requests/hour unauthenticated), inform the user.
- Handle network errors gracefully.
Optional — cache results: Save analysis to a JSON file so repeated queries for the same user are instant.
Example repository language counter:
languages = {}
for repo in repos:
lang = repo.get("language")
if lang:
languages[lang] = languages.get(lang, 0) + 1
most_used = max(languages, key=languages.get)
Stretch goals:
- Add an option to export the report as a formatted text file.
- Use the
datetimemodule to show when the user was created and last active. - Add a simple rate-limit tracker.
6. Brief Review Summary
| Concept | Key Points |
|---|---|
| API | A set of rules for programs to communicate over HTTP |
| JSON | Universal data format; looks like Python dicts and lists |
requests.get() | Makes a GET request; returns a Response object |
.json() | Parses response body into Python dict/list |
.raise_for_status() | Raises exception for 4xx/5xx status codes |
| Query parameters | Use params= dict for GET; json= dict for POST |
| Error handling | Catch Timeout, ConnectionError, HTTPError |
| API Client class | Encapsulates API logic for reusability |
7. Preview of Next Topic — Day 20
Tomorrow we enter the world of concurrent programming:
- Multithreading — running multiple tasks in parallel within a single process
- Multiprocessing — spreading work across multiple CPU cores
- The
threadingandmultiprocessingmodules - When to use each — the GIL explained
- Real‑world use cases: web scraping, CPU‑bound work, I/O‑bound work
π― Your Action Items for Day 19:
- ✅ Complete all 5 exercises
- ✅ Build the GitHub Profile Analyzer CLI
- ✅ Explore a public API of your choice (PokΓ©mon, Star Wars, weather)
- ✅ Practice writing API calls that handle all error conditions gracefully
Comments
Post a Comment
Leave us your comments here...