Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
414: Building a REST API Client Tool
Listen, in a professional codebase, you'll rarely see requests.get() calls scattered randomly throughout the business logic. It's messy, it makes testing a nightmare, and if the API provider changes their base URL or authentication scheme, you're stuck with a massive find-and-replace mission. Instead, we build a "Client"—a dedicated wrapper class that encapsulates the API's quirks.
For this lesson, we're going to build a small tool to pull data from the GitHub API. I want to be able to fetch basic repository information and a list of open issues without worrying about the underlying HTTP headers every single time.
Designing the Client Wrapper
I always start by creating a class that handles the "plumbing." This includes the base URL and any authentication tokens. By putting these in the __init__ method, the rest of the class can just focus on the specific endpoints.
import requests
class GitHubClient:
def __init__(self, token=None):
self.base_url = "https://api.github.com"
self.session = requests.Session()
if token:
# Using a session is much more efficient for multiple requests
self.session.headers.update({"Authorization": f"token {token}"})
self.session.headers.update({"Accept": "application/vnd.github.v3+json"})
I used requests.Session() here instead of just calling requests.get(). It's a habit I've picked up over the years; sessions reuse the underlying TCP connection, which makes your tool significantly faster when you're making multiple calls to the same host.
Handling the Request Logic
Now, I need a generic way to make requests. I don't want to write try-except blocks inside every single method I add to this class. I'll create a private helper method to handle the actual network call.
def _request(self, method, endpoint, params=None):
url = f"{self.base_url}/{endpoint}"
response = self.session.request(method, url, params=params)
return response.json()
def get_repo_details(self, owner, repo):
endpoint = f"repos/{owner}/{repo}"
return self._request("GET", endpoint)
Fixing the "Happy Path" Assumption
Here is where I usually mess up when I'm rushing—and I did it just now. I wrote the _request method assuming the API will always return a 200 OK. But what happens if the repo doesn't exist? Or if the token expires? GitHub will return a 404 or 401, and while .json() might still work, the data inside will be an error message, not the repo details. My get_repo_details method would then return an error dictionary, and the calling code would probably crash with a KeyError when trying to access a field like 'stargazers_count'.
I need to actually check the status code. I'll use raise_for_status() to turn those HTTP errors into Python exceptions that we can actually handle.
def _request(self, method, endpoint, params=None):
url = f"{self.base_url}/{endpoint}"
response = self.session.request(method, url, params=params)
# This is the fix: if the status is 4xx or 5xx, it raises an HTTPError
response.raise_for_status()
return response.json()
Adding Resource-Specific Methods
Now that the plumbing is robust, adding new functionality is trivial. I want to be able to get open issues for a repo. I can just leverage the _request method and pass in some query parameters.
def get_open_issues(self, owner, repo):
endpoint = f"repos/{owner}/{repo}/issues"
params = {"state": "open"}
return self._request("GET", endpoint, params=params)
# Let's put it to work
client = GitHubClient()
try:
repo_info = client.get_repo_details("psf", "requests")
print(f"Stars: {repo_info['stargazers_count']}")
issues = client.get_open_issues("psf", "requests")
print(f"Open Issues: {len(issues)}")
except requests.exceptions.HTTPError as e:
print(f"API Error occurred: {e}")
By structuring it this way, the "user" of the GitHubClient class doesn't need to know about URLs, headers, or the requests library itself. They just call a method and get data back. That's the essence of a clean API client.
📋 Practical Task
Exercise: Building a Space-X Launch Data Client
Using the patterns from this lesson, create a Python class called SpaceXClient that interacts with the public SpaceX API (https://api.spacexdata.com/v4). Your client should meet the following requirements:
- Initialize with the base URL in the
__init__method. - Implement a private
_requestmethod that uses arequests.Sessionand includesresponse.raise_for_status()to handle errors. - Implement a public method
get_latest_launch()that fetches data from the/launches/latestendpoint. - Implement a public method
get_launches_by_flight_number(flight_number)that fetches data from the/launches/{flight_number}endpoint.
Test your client by printing the name of the latest launch and the details of flight number 1. Ensure you wrap your test calls in a try-except block to catch potential HTTPError exceptions.
There are no comments for now.