๐ Lesson 10: Working with APIs
Learn to connect your Python programs to the outside world. You'll use the requests library to interact with REST APIs, parse JSON data, handle authentication, manage errors gracefully, and build a mini data dashboard.
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Explain what an API is and how REST APIs work
- Use the
requestslibrary to make HTTP GET and POST requests - Parse and navigate JSON responses
- Send query parameters, headers, and request bodies
- Authenticate with API keys and Bearer tokens
- Handle HTTP errors, timeouts, and connection failures
- Build a Python script that fetches live data from a public API
Estimated Time: 60 minutes
Prerequisites: Dictionaries, f-strings, error handling (Lesson 5), virtual environments (Lesson 9)
In This Lesson
๐ What Is an API?
An API (Application Programming Interface) is a set of rules that lets one piece of software talk to another. When you check the weather on your phone, the app doesn't generate forecasts itself โ it sends a request to a weather service's API and displays the response.
Think of it like ordering at a restaurant:
- You (the client) look at the menu (the API documentation)
- You give your order (the request) to the waiter (the API)
- The kitchen (the server) prepares your food and the waiter brings it back (the response)
๐ Web APIs vs. Other APIs
Python has many kinds of APIs โ the os module is an API for your operating system, and list.append() is part of the list API. In this lesson, we focus on Web APIs โ services you talk to over the internet using HTTP.
๐ก REST & HTTP Basics
Most web APIs follow the REST (Representational State Transfer) pattern. REST APIs use standard HTTP methods to perform operations on resources โ things like users, posts, products, or weather data โ identified by URLs.
HTTP Methods
Each HTTP method signals a different intent:
| Method | Purpose | Example |
|---|---|---|
GET |
Retrieve data | Fetch a list of users |
POST |
Create new data | Submit a new blog post |
PUT |
Replace existing data | Update an entire user profile |
PATCH |
Partially update data | Change just a user's email |
DELETE |
Remove data | Delete a comment |
HTTP Status Codes
Every response includes a status code โ a three-digit number that tells you what happened:
URLs & Endpoints
A REST API organizes resources into predictable URLs called endpoints:
# Base URL
https://api.example.com/v1
# Endpoints (resources)
GET /users โ List all users
GET /users/42 โ Get user with id 42
POST /users โ Create a new user
PUT /users/42 โ Replace user 42
DELETE /users/42 โ Delete user 42
๐ก REST Is a Convention
REST isn't a strict protocol โ it's a set of design conventions. Not every API follows them perfectly. Always read the API's documentation to understand how it expects you to make requests.
๐ฆ The requests Library
Python's standard library includes urllib for making HTTP requests, but it's verbose and awkward. The third-party requests library is the de facto standard โ simple, powerful, and beloved by the Python community.
Installation
Install it in your virtual environment (remember Lesson 9?):
# Create and activate a venv first!
python -m venv .venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
# Install requests
pip install requests
Your First Request
import requests
response = requests.get("https://httpbin.org/get")
print(response.status_code) # 200
print(type(response)) # <class 'requests.models.Response'>
That's it โ one line to make an HTTP GET request. The requests.get() function returns a Response object packed with useful attributes:
| Attribute / Method | Description |
|---|---|
response.status_code |
HTTP status code (200, 404, etc.) |
response.text |
Response body as a string |
response.json() |
Parse JSON body into Python dict/list |
response.headers |
Response headers (dict-like) |
response.url |
The final URL (after redirects) |
response.ok |
True if status is 200โ299 |
response.raise_for_status() |
Raise HTTPError if status โฅ 400 |
๐ฅ Making GET Requests
GET is the most common HTTP method โ it retrieves data without changing anything on the server. Let's use a free public API to fetch real data.
Example: Fetching a Random User
import requests
url = "https://randomuser.me/api/"
response = requests.get(url)
if response.ok:
data = response.json()
user = data["results"][0]
name = f"{user['name']['first']} {user['name']['last']}"
email = user["email"]
country = user["location"]["country"]
print(f"Name: {name}")
print(f"Email: {email}")
print(f"Country: {country}")
else:
print(f"Request failed: {response.status_code}")
Output (example โ results vary):
Name: Sofia Andersen
Email: sofia.andersen@example.com
Country: Denmark
Example: Fetching Multiple Items
import requests
# JSONPlaceholder โ a free fake REST API for testing
url = "https://jsonplaceholder.typicode.com/posts"
response = requests.get(url)
posts = response.json() # Returns a list of 100 posts
print(f"Total posts: {len(posts)}")
print(f"First post title: {posts[0]['title']}")
# Print the first 3 posts
for post in posts[:3]:
print(f"\n[Post {post['id']}] {post['title']}")
print(f" {post['body'][:80]}...")
โก Don't Hammer APIs
Public APIs have rate limits โ rules about how many requests you can make per minute/hour. Exceeding them returns a 429 Too Many Requests status. Always read the API docs for rate limit policies, and add delays (time.sleep()) in loops that make repeated requests.
๐ Working with JSON
JSON (JavaScript Object Notation) is the standard data format for web APIs. It maps directly to Python data structures:
| JSON Type | Python Type | Example |
|---|---|---|
object {} |
dict |
{"name": "Ada"} |
array [] |
list |
[1, 2, 3] |
| string | str |
"hello" |
| number | int / float |
42, 3.14 |
| true / false | True / False |
true |
| null | None |
null |
Parsing JSON from a Response
import requests
response = requests.get("https://jsonplaceholder.typicode.com/users/1")
user = response.json() # Parses JSON string โ Python dict
# Navigate the nested structure
print(user["name"]) # "Leanne Graham"
print(user["address"]["city"]) # "Gwenborough"
print(user["company"]["catchPhrase"]) # "Multi-layered client-server neural-net"
The json Module
Python's built-in json module handles conversion between JSON strings and Python objects. You'll use it when saving API data to files:
import json
# Python dict โ JSON string
data = {"name": "Ada", "languages": ["Python", "Haskell"]}
json_string = json.dumps(data, indent=2)
print(json_string)
# JSON string โ Python dict
parsed = json.loads(json_string)
print(parsed["name"]) # "Ada"
# Save to file
with open("user_data.json", "w") as f:
json.dump(data, f, indent=2)
# Load from file
with open("user_data.json") as f:
loaded = json.load(f)
print(loaded) # {'name': 'Ada', 'languages': ['Python', 'Haskell']}
(dict, list)"] -->|"json.dumps()"| B["๐ JSON String"] B -->|"json.loads()"| A A -->|"json.dump(file)"| C["๐ JSON File"] C -->|"json.load(file)"| A style A fill:#eff6ff,stroke:#3b82f6,color:#1e293b style B fill:#f0fdf4,stroke:#22c55e,color:#1e293b style C fill:#fefce8,stroke:#f59e0b,color:#1e293b
๐ง .json() vs json.loads()
Calling response.json() is a shortcut for json.loads(response.text). Both give you the same Python dict/list. Use response.json() when working with API responses โ it's cleaner. Use the json module directly when reading/writing files or working with raw JSON strings.
๐ง Query Parameters & Headers
Query Parameters
Many APIs accept query parameters โ key-value pairs appended to the URL after a ?. Instead of building the URL string manually, pass a params dict:
import requests
# โ Manual URL building โ error-prone, no encoding
url = "https://api.example.com/search?q=python+tutorial&limit=5"
# โ
Use the params argument โ clean and auto-encoded
url = "https://api.example.com/search"
params = {
"q": "python tutorial",
"limit": 5,
"sort": "relevance"
}
response = requests.get(url, params=params)
print(response.url)
# https://api.example.com/search?q=python+tutorial&limit=5&sort=relevance
Real Example: Searching the Open Library API
import requests
url = "https://openlibrary.org/search.json"
params = {"q": "dune frank herbert", "limit": 3}
response = requests.get(url, params=params)
data = response.json()
print(f"Found {data['numFound']} results.\n")
for book in data["docs"][:3]:
title = book.get("title", "Unknown")
author = ", ".join(book.get("author_name", ["Unknown"]))
year = book.get("first_publish_year", "N/A")
print(f"๐ {title}")
print(f" by {author} ({year})\n")
Custom Headers
HTTP headers carry metadata about the request โ content type, authentication, user-agent, etc. Pass them via the headers argument:
import requests
headers = {
"Accept": "application/json",
"User-Agent": "MyPythonApp/1.0"
}
response = requests.get(
"https://httpbin.org/headers",
headers=headers
)
print(response.json())
๐ก When to Use Custom Headers
Acceptโ Tell the API what format you want (application/json,text/xml)Authorizationโ Send API keys or tokens (covered in the Authentication section)Content-Typeโ Describe the format of data you're sending (application/json)User-Agentโ Identify your application (some APIs require it)
๐ค POST Requests & Sending Data
While GET retrieves data, POST sends data to the server to create or process something. Use the json parameter to send JSON data automatically:
import requests
url = "https://jsonplaceholder.typicode.com/posts"
new_post = {
"title": "Learning APIs with Python",
"body": "The requests library makes it incredibly easy!",
"userId": 1
}
response = requests.post(url, json=new_post)
print(response.status_code) # 201 (Created)
print(response.json())
# {'title': 'Learning APIs with Python', 'body': '...', 'userId': 1, 'id': 101}
๐ json= vs data=
The json= parameter automatically serializes your dict to JSON and sets the Content-Type header to application/json. The older data= parameter sends form-encoded data (application/x-www-form-urlencoded). For modern REST APIs, json= is almost always what you want.
Other HTTP Methods
import requests
base = "https://jsonplaceholder.typicode.com/posts"
# PUT โ Replace entire resource
response = requests.put(f"{base}/1", json={
"id": 1,
"title": "Updated Title",
"body": "Completely replaced body.",
"userId": 1
})
print(f"PUT: {response.status_code}") # 200
# PATCH โ Partial update
response = requests.patch(f"{base}/1", json={
"title": "Only Changing the Title"
})
print(f"PATCH: {response.status_code}") # 200
# DELETE โ Remove resource
response = requests.delete(f"{base}/1")
print(f"DELETE: {response.status_code}") # 200
Read data"] C -->|"requests.post()"| POST["๐ค POST
Create data"] C -->|"requests.put()"| PUT["๐ PUT
Replace data"] C -->|"requests.patch()"| PATCH["โ๏ธ PATCH
Update data"] C -->|"requests.delete()"| DEL["๐๏ธ DELETE
Remove data"] GET --> S["๐ Server"] POST --> S PUT --> S PATCH --> S DEL --> S style C fill:#eff6ff,stroke:#3b82f6,color:#1e293b style S fill:#f0fdf4,stroke:#22c55e,color:#1e293b style GET fill:#f8fafc,stroke:#22c55e,color:#1e293b style POST fill:#f8fafc,stroke:#6366f1,color:#1e293b style PUT fill:#f8fafc,stroke:#f59e0b,color:#1e293b style PATCH fill:#f8fafc,stroke:#f59e0b,color:#1e293b style DEL fill:#f8fafc,stroke:#ef4444,color:#1e293b
๐ Authentication
Many APIs require you to prove who you are before they'll respond. The most common methods are API keys and Bearer tokens.
API Key in Query Parameters
Some APIs expect the key as a query parameter:
import requests
API_KEY = "your_api_key_here" # Get from the service's dashboard
response = requests.get(
"https://api.example.com/data",
params={"api_key": API_KEY}
)
API Key in Headers
Others expect it in a custom header:
import requests
API_KEY = "your_api_key_here"
response = requests.get(
"https://api.example.com/data",
headers={"X-API-Key": API_KEY}
)
Bearer Token (OAuth2-style)
Many modern APIs use a Bearer token in the Authorization header:
import requests
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5..." # Obtained through login/OAuth flow
response = requests.get(
"https://api.example.com/me",
headers={"Authorization": f"Bearer {TOKEN}"}
)
๐ Never Hardcode Secrets!
API keys and tokens should never be committed to Git or hardcoded in scripts. Use environment variables instead:
import os
import requests
API_KEY = os.environ.get("MY_API_KEY")
if not API_KEY:
raise RuntimeError("Set MY_API_KEY environment variable")
response = requests.get(
"https://api.example.com/data",
headers={"Authorization": f"Bearer {API_KEY}"}
)
Set the variable before running your script:
# Linux/Mac
export MY_API_KEY="abc123secret"
# Windows (PowerShell)
$env:MY_API_KEY = "abc123secret"
For projects, use a .env file with the python-dotenv library and add .env to your .gitignore.
๐ก๏ธ Error Handling & Retries
Real-world APIs fail. Servers go down, networks drop, rate limits kick in. Robust code handles all of these gracefully.
Basic Error Handling
import requests
try:
response = requests.get(
"https://api.example.com/data",
timeout=10 # seconds โ always set a timeout!
)
response.raise_for_status() # Raises HTTPError for 4xx/5xx
data = response.json()
except requests.exceptions.Timeout:
print("โฐ Request timed out โ try again later")
except requests.exceptions.ConnectionError:
print("๐ Could not connect โ check your network")
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 == 429:
print(" Rate limited โ slow down!")
except requests.exceptions.RequestException as e:
print(f"โ Request failed: {e}")
Timeouts
Always set a timeout. Without one, your program could hang forever waiting for a server that never responds:
# timeout=10 โ max 10 seconds total
response = requests.get(url, timeout=10)
# Separate connect and read timeouts
response = requests.get(url, timeout=(3.05, 27))
# connect read
Simple Retry Logic
import time
import requests
def fetch_with_retries(url, max_retries=3, backoff_factor=1):
"""Fetch a URL with exponential backoff on failure."""
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response
except (requests.exceptions.ConnectionError,
requests.exceptions.Timeout) as e:
wait = backoff_factor * (2 ** attempt) # 1s, 2s, 4s
print(f"Attempt {attempt + 1} failed: {e}")
print(f"Retrying in {wait}s...")
time.sleep(wait)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
wait = backoff_factor * (2 ** attempt)
print(f"Rate limited. Retrying in {wait}s...")
time.sleep(wait)
else:
raise # Don't retry other HTTP errors
raise requests.exceptions.ConnectionError(
f"Failed after {max_retries} retries"
)
# Usage
response = fetch_with_retries("https://api.example.com/data")
data = response.json()
โ
Production Tip: urllib3.util.Retry
For production code, use the requests library's built-in retry adapter instead of writing your own loop:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retries = Retry(total=3, backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504])
session.mount("https://", HTTPAdapter(max_retries=retries))
response = session.get("https://api.example.com/data", timeout=10)
๐๏ธ Mini-Project: Country Info Dashboard
Let's bring it all together. We'll build a command-line dashboard that fetches country data from the free REST Countries API and presents it neatly.
"""
country_dashboard.py โ Fetch and display country info from REST Countries API.
"""
import requests
import json
def fetch_country(name):
"""Fetch country data by name. Returns dict or None."""
url = f"https://restcountries.com/v3.1/name/{name}"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
countries = response.json()
return countries[0] # Take first match
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print(f"โ Country '{name}' not found.")
else:
print(f"โ HTTP error: {e.response.status_code}")
except requests.exceptions.RequestException as e:
print(f"โ Request failed: {e}")
return None
def display_country(country):
"""Pretty-print country information."""
name = country["name"]["common"]
official = country["name"]["official"]
capital = ", ".join(country.get("capital", ["N/A"]))
region = country.get("region", "N/A")
subregion = country.get("subregion", "N/A")
population = country.get("population", 0)
area = country.get("area", 0)
languages = ", ".join(country.get("languages", {}).values())
currencies = ", ".join(
f"{c['name']} ({c.get('symbol', '')})"
for c in country.get("currencies", {}).values()
)
flag = country.get("flag", "")
print(f"\n{'=' * 50}")
print(f" {flag} {name}")
print(f"{'=' * 50}")
print(f" Official Name : {official}")
print(f" Capital : {capital}")
print(f" Region : {region} / {subregion}")
print(f" Population : {population:,}")
print(f" Area : {area:,.0f} kmยฒ")
print(f" Languages : {languages}")
print(f" Currencies : {currencies}")
print(f"{'=' * 50}")
def compare_countries(names):
"""Fetch and compare multiple countries side by side."""
countries = []
for name in names:
c = fetch_country(name)
if c:
countries.append(c)
if len(countries) < 2:
print("Need at least 2 valid countries to compare.")
return
print(f"\n{'':>20}", end="")
for c in countries:
print(f"{c['name']['common']:>20}", end="")
print()
print("-" * (20 + 20 * len(countries)))
# Population
print(f"{'Population':>20}", end="")
for c in countries:
print(f"{c.get('population', 0):>20,}", end="")
print()
# Area
print(f"{'Area (kmยฒ)':>20}", end="")
for c in countries:
print(f"{c.get('area', 0):>20,.0f}", end="")
print()
# Region
print(f"{'Region':>20}", end="")
for c in countries:
print(f"{c.get('region', 'N/A'):>20}", end="")
print()
# Capital
print(f"{'Capital':>20}", end="")
for c in countries:
cap = ", ".join(c.get("capital", ["N/A"]))
print(f"{cap:>20}", end="")
print()
def main():
"""Interactive country dashboard."""
print("๐ Country Info Dashboard")
print("Commands: search, compare, quit\n")
while True:
command = input(">>> ").strip().lower()
if command == "quit":
print("Goodbye! ๐")
break
elif command == "search":
name = input("Country name: ").strip()
country = fetch_country(name)
if country:
display_country(country)
elif command == "compare":
names = input("Countries (comma-separated): ").strip()
name_list = [n.strip() for n in names.split(",")]
compare_countries(name_list)
else:
print("Unknown command. Try: search, compare, quit")
if __name__ == "__main__":
main()
Sample Output:
๐ Country Info Dashboard
Commands: search, compare, quit
>>> search
Country name: philippines
==================================================
๐ต๐ญ Philippines
==================================================
Official Name : Republic of the Philippines
Capital : Manila
Region : Asia / South-Eastern Asia
Population : 109,581,078
Area : 342,353 kmยฒ
Languages : English, Filipino
Currencies : Philippine peso (โฑ)
==================================================
>>> compare
Countries (comma-separated): japan, south korea, philippines
Japan South Korea Philippines
------------------------------------------------------------------------
Population 125,836,021 51,269,185 109,581,078
Area (kmยฒ) 377,930 100,210 342,353
Region Asia Asia Asia
Capital Tokyo Seoul Manila
>>> quit
Goodbye! ๐
๐ก Ideas to Extend This
- Save results to a JSON file (
json.dump) - Add a "random" command using
https://restcountries.com/v3.1/all+random.choice() - Use
argparseto accept country names as command-line arguments - Add population density calculation (
population / area)
๐ช Hands-on Exercises
Exercise 1: Book Search Tool
Write a function called search_books(query, limit=5) that:
- Searches the Open Library Search API at
https://openlibrary.org/search.json - Accepts a search query and a result limit
- Returns a list of dicts with keys:
title,author,year - Handles errors gracefully (network failures, invalid responses)
Then write a main() that asks the user for a search term, calls search_books(), and prints the results in a numbered list.
๐ก Hint
The Open Library API accepts q and limit as query parameters. Each result in data["docs"] has title, author_name (a list), and first_publish_year. Use .get() for safe access to keys that might be missing.
โ Solution
import requests
def search_books(query, limit=5):
"""Search Open Library and return a list of book dicts."""
url = "https://openlibrary.org/search.json"
params = {"q": query, "limit": limit}
try:
response = requests.get(url, params=params, timeout=15)
response.raise_for_status()
data = response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching books: {e}")
return []
books = []
for doc in data.get("docs", []):
books.append({
"title": doc.get("title", "Unknown Title"),
"author": ", ".join(doc.get("author_name", ["Unknown"])),
"year": doc.get("first_publish_year", "N/A"),
})
return books
def main():
query = input("Search for a book: ").strip()
if not query:
print("No search term provided.")
return
print(f"\nSearching for '{query}'...\n")
results = search_books(query)
if not results:
print("No results found.")
return
for i, book in enumerate(results, 1):
print(f" {i}. {book['title']}")
print(f" by {book['author']} ({book['year']})")
if __name__ == "__main__":
main()
Exercise 2: GitHub User Profiler
Write a script that takes a GitHub username and uses the GitHub REST API (no authentication needed for public data) to display:
- The user's name, bio, location, and number of public repos
- Their 5 most recently updated repositories (name, description, language, stars)
Endpoints:
- User info:
https://api.github.com/users/{username} - User repos:
https://api.github.com/users/{username}/repos(acceptssort=updatedandper_page=5params)
๐ก Hint
Make two separate requests.get() calls โ one for the user profile, one for their repos. GitHub recommends sending a User-Agent header. The repos endpoint supports sort and per_page query parameters. Star count is under stargazers_count.
โ Solution
import requests
GITHUB_API = "https://api.github.com"
HEADERS = {"User-Agent": "PythonCourseLearner/1.0"}
def get_user(username):
"""Fetch GitHub user profile."""
url = f"{GITHUB_API}/users/{username}"
response = requests.get(url, headers=HEADERS, timeout=10)
response.raise_for_status()
return response.json()
def get_repos(username, count=5):
"""Fetch user's most recently updated repos."""
url = f"{GITHUB_API}/users/{username}/repos"
params = {"sort": "updated", "per_page": count}
response = requests.get(
url, headers=HEADERS, params=params, timeout=10
)
response.raise_for_status()
return response.json()
def display_profile(username):
"""Display a GitHub user profile and recent repos."""
try:
user = get_user(username)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print(f"User '{username}' not found.")
else:
print(f"Error: {e.response.status_code}")
return
except requests.exceptions.RequestException as e:
print(f"Connection error: {e}")
return
print(f"\n๐ค {user.get('name', username)}")
print(f" @{user['login']}")
if user.get("bio"):
print(f" {user['bio']}")
if user.get("location"):
print(f" ๐ {user['location']}")
print(f" ๐ฆ {user['public_repos']} public repos")
print(f" ๐ฅ {user['followers']} followers")
try:
repos = get_repos(username)
except requests.exceptions.RequestException:
print(" (Could not fetch repos)")
return
print(f"\n ๐ Recently Updated Repos:")
for repo in repos:
lang = repo.get("language") or "โ"
stars = repo["stargazers_count"]
desc = repo.get("description") or "No description"
print(f" โญ {stars:>4} {repo['name']} [{lang}]")
print(f" {desc[:60]}")
if __name__ == "__main__":
username = input("GitHub username: ").strip()
if username:
display_profile(username)
๐ Summary
In this lesson you learned to connect Python programs to the outside world through REST APIs. Here's what we covered:
- APIs are interfaces that let programs talk to servers โ REST APIs use HTTP methods and JSON
- The
requestslibrary provides a clean, intuitive API for HTTP:get(),post(),put(),patch(),delete() - JSON maps directly to Python dicts and lists โ use
response.json()or thejsonmodule - Query parameters go in the
params=argument; custom headers go inheaders= - POST with
json=sends data; the server typically returns201 Created - Authentication uses API keys (in params or headers) or Bearer tokens โ keep secrets in environment variables
- Error handling means catching
Timeout,ConnectionError, andHTTPError, plus always settingtimeout= - Retry logic with exponential backoff handles transient failures gracefully
๐ Quick Reference
| Task | Code |
|---|---|
| GET request | requests.get(url) |
| POST with JSON | requests.post(url, json=data) |
| Query params | requests.get(url, params={"q": "term"}) |
| Custom headers | requests.get(url, headers={"Auth": "..."}) |
| Set timeout | requests.get(url, timeout=10) |
| Check success | response.ok or response.raise_for_status() |
| Parse JSON | data = response.json() |
| Save JSON to file | json.dump(data, file, indent=2) |
| Env variable | os.environ.get("API_KEY") |
๐ Additional Resources
- requests Documentation
- Python Docs โ json Module
- httpbin.org โ HTTP Testing Service
- JSONPlaceholder โ Fake REST API
- REST Countries API
- Public APIs List โ Massive Collection of Free APIs
๐ What's Next?
In the next lesson, we'll learn Database Basics with SQLite โ SQL fundamentals, Python's sqlite3 module, parameterized queries, and building data-backed applications. Combined with APIs, you'll be able to fetch data from the web and store it locally.
๐งช Knowledge Check
Test your understanding with these quick questions:
Question 1
What does response.raise_for_status() do?
Question 2
Which is the correct way to pass query parameters with requests?
Question 3
Why should you always set a timeout on API requests?