Practical API Example0%

Practical API Example

Beginner12 min readUpdated: Jul 11, 2026
Study Materials

Practical API Example: Building an API Client Class in Python

In professional software development, API calls should not be scattered haphazardly throughout codebase files. Instead, best practices dictate encapsulating HTTP interactions inside a dedicated API Client Class that manages base URLs, authentication tokens, connection pooling via requests.Session, pagination, and error handling.


1. Utilizing requests.Session for Connection Pooling

When you call requests.get() repeatedly, Python opens and closes a new TCP connection for every single request. Using requests.Session() reuses the underlying TCP connection (via HTTP keep-alive), resulting in significantly faster requests and shared headers/cookies.

Python
import requests
 
# Reusable session object with persistent headers
session = requests.Session()
session.headers.update({
"User-Agent": "DevClient/1.0",
"Accept": "application/json"
})
 
# Both requests share the same TCP connection pool:
r1 = session.get("https://jsonplaceholder.typicode.com/users/1")
r2 = session.get("https://jsonplaceholder.typicode.com/users/2")

2. Designing a Complete API Client: GitHub Public API

Let's build a clean, production-ready client for querying public GitHub repositories and user profiles:

Visual Architecture & Process Flow

How data and code flow step-by-step

Flowchart
Step 1
def _request
self, endpoint: str, params: Optional[Dict[str, Any]] = None
Step 2
Any:

3. Consuming the API Client

Notice how clean and intuitive the caller code becomes when the HTTP details are cleanly encapsulated:

Python
def main():
client = GitHubClient()
 
# 1. Fetch user profile
username = "octocat"
profile = client.get_user_profile(username)
 
if profile:
print("=" * 45)
print(f"Profile: {profile.get('name')} (@{profile.get('login')})")
print(f"Bio: {profile.get('bio')}")
print(f"Public Repos: {profile.get('public_repos')}")
print(f"Followers: {profile.get('followers')}")
print("=" * 45)
 
# 2. Fetch top repositories
repos = client.get_user_repositories(username, limit=3)
print(f"\nTop Repositories for @{username}:")
for r in repos:
print(f" - {r['name']}: ⭐ {r['stargazers_count']} | Language: {r.get('language') or 'N/A'}")
 
if __name__ == "__main__":
main()

4. Key Client Design Principles

  1. 1
    Keep Base URLs Configurable: Define BASE_URL as a class or instance variable to facilitate swapping staging/production environments.
  2. 2
    Centralize Error Handling: Use an internal _request() method so retry logic, rate limit checking, and timeouts are handled in one place.
  3. 3
    Inspect Rate-Limit Headers: Many APIs return X-RateLimit-Remaining and X-RateLimit-Reset headers. Monitoring these prevents unexpected 403 Forbidden errors.
  4. 4
    Use Session Objects: requests.Session handles HTTP connection pooling and avoids recreating TCP/TLS handshakes on every call.

Multiple Choice Questions

1. What is the primary performance benefit of using requests.Session() over calling requests.get() repeatedly?

A. It compiles Python scripts into C binaries B. It reuses underlying TCP connections (connection pooling) across multiple requests C. It bypasses internet firewalls D. It compresses all response strings into gzip automatically Answer: B Explanation: requests.Session() keeps TCP connections open via HTTP Keep-Alive, significantly speeding up multiple requests to the same host.


2. What HTTP status code typically indicates that an API client has exceeded its rate limit quota?

A. 200 OK B. 404 Not Found C. 429 Too Many Requests (or 403 Forbidden with rate headers) D. 500 Internal Server Error Answer: C Explanation: Standard APIs return HTTP 429 Too Many Requests (or sometimes 403 Forbidden) when an API consumer exceeds the permitted request quota.


3. Why is it advantageous to route all API calls through an internal _request() helper method?

A. It eliminates the need for unit testing B. Centralizing request execution allows uniform error handling, header injection, logging, and timeouts C. Python requires helper methods for all network operations D. It makes the class immutable Answer: B Explanation: Routing calls through a single method ensures consistent error handling, header management, and timeout configurations without code duplication.


4. Which header returned by GitHub and many modern APIs informs you how many requests remain in your quota?

A. X-Cache-Status B. X-RateLimit-Remaining C. ETag D. Content-Encoding Answer: B Explanation: The X-RateLimit-Remaining response header indicates the number of allowed requests left in the current rate limit window.


5. In our GitHubClient, how are query parameters passed to limit the number of repositories returned?

A. By appending #limit=5 to the URL B. By passing params={"per_page": limit} to the session GET request C. By modifying the HTTP Host header D. By setting an environment variable Answer: B Explanation: Passing a dictionary to the params argument in requests dynamically appends ?per_page=... to the final request URL.


Next Lesson

Project: Weather Data Fetcher

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Related Lessons

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext