python
exercises
exercises.py🐍python
"""
Networking & APIs - Exercises
Practice HTTP requests, API interactions, and JSON handling
"""
# ============================================================
# EXERCISE 1: URL Parser
# ============================================================
"""
Create a function that parses a URL and returns its components.
Example:
>>> parse_url("https://api.example.com:8080/users/123?format=json&active=true#section1")
{
'scheme': 'https',
'host': 'api.example.com',
'port': 8080,
'path': '/users/123',
'query': {'format': ['json'], 'active': ['true']},
'fragment': 'section1'
}
"""
from urllib.parse import urlparse, parse_qs
def parse_url(url):
# YOUR CODE HERE
pass
# Test
# test_url = "https://api.example.com:8080/users/123?format=json&active=true#section1"
# print(parse_url(test_url))
# ============================================================
# EXERCISE 2: Query String Builder
# ============================================================
"""
Create a function that builds a URL with query parameters.
Handle special characters properly (URL encoding).
Example:
>>> build_url("https://api.example.com/search", {"q": "python programming", "page": 1, "filter": "name=test"})
'https://api.example.com/search?q=python+programming&page=1&filter=name%3Dtest'
"""
from urllib.parse import urlencode
def build_url(base_url, params):
# YOUR CODE HERE
pass
# Test
# print(build_url("https://api.example.com/search", {"q": "python programming", "page": 1}))
# ============================================================
# EXERCISE 3: JSON Data Validator
# ============================================================
"""
Create a function that validates if a string is valid JSON
and optionally checks if it contains required keys.
Example:
>>> validate_json('{"name": "John", "age": 30}')
(True, {'name': 'John', 'age': 30})
>>> validate_json('{"name": "John"}', required_keys=['name', 'age'])
(False, "Missing required keys: ['age']")
>>> validate_json('not valid json')
(False, "Invalid JSON: ...")
"""
import json
def validate_json(json_string, required_keys=None):
# YOUR CODE HERE
pass
# Test
# print(validate_json('{"name": "John", "age": 30}'))
# print(validate_json('{"name": "John"}', required_keys=['name', 'age']))
# print(validate_json('not json'))
# ============================================================
# EXERCISE 4: API Response Handler
# ============================================================
"""
Create a class that handles API responses with proper error handling,
status code checking, and data extraction.
Example:
>>> handler = APIResponseHandler(response)
>>> handler.is_success()
True
>>> handler.get_data()
{'users': [...]}
>>> handler.get_header('Content-Type')
'application/json'
"""
class APIResponseHandler:
"""Handle API responses with proper error checking."""
def __init__(self, response):
# YOUR CODE HERE
pass
def is_success(self):
"""Return True if status code is 2xx."""
pass
def get_status(self):
"""Return status code."""
pass
def get_data(self):
"""Return parsed JSON data or None."""
pass
def get_header(self, header_name, default=None):
"""Get a specific header value."""
pass
def get_error_message(self):
"""Return error message if not successful."""
pass
# ============================================================
# EXERCISE 5: Rate Limiter
# ============================================================
"""
Create a rate limiter class that ensures API calls don't exceed
a specified rate (e.g., 10 requests per minute).
Example:
>>> limiter = RateLimiter(max_requests=10, period=60) # 10 per minute
>>> limiter.can_proceed()
True
>>> limiter.record_request()
>>> # After 10 requests:
>>> limiter.can_proceed()
False
>>> limiter.wait_time()
45.5 # seconds to wait
"""
import time
from collections import deque
class RateLimiter:
"""Rate limiter for API requests."""
def __init__(self, max_requests, period):
"""
Args:
max_requests: Maximum number of requests allowed
period: Time period in seconds
"""
# YOUR CODE HERE
pass
def can_proceed(self):
"""Check if a request can be made."""
pass
def record_request(self):
"""Record that a request was made."""
pass
def wait_time(self):
"""Return how long to wait before next request (in seconds)."""
pass
# ============================================================
# EXERCISE 6: Retry Decorator
# ============================================================
"""
Create a decorator that retries a function on failure with
exponential backoff.
Example:
@retry(max_attempts=3, backoff_factor=2)
def fetch_data(url):
response = requests.get(url)
response.raise_for_status()
return response.json()
# Will retry up to 3 times with delays: 1s, 2s, 4s
"""
import time
import functools
def retry(max_attempts=3, backoff_factor=2, exceptions=(Exception,)):
"""
Retry decorator with exponential backoff.
Args:
max_attempts: Maximum number of attempts
backoff_factor: Multiplier for delay between retries
exceptions: Tuple of exceptions to catch and retry
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# YOUR CODE HERE
pass
return wrapper
return decorator
# Test
# @retry(max_attempts=3, backoff_factor=2)
# def unreliable_function():
# import random
# if random.random() < 0.7:
# raise Exception("Random failure")
# return "Success!"
# ============================================================
# EXERCISE 7: Simple HTTP Client
# ============================================================
"""
Create a simple HTTP client class with common operations.
Use the requests library internally.
Example:
>>> client = SimpleHTTPClient("https://api.example.com")
>>> client.get("/users")
>>> client.post("/users", {"name": "John"})
>>> client.put("/users/1", {"name": "Jane"})
>>> client.delete("/users/1")
"""
class SimpleHTTPClient:
"""Simple HTTP client for API interactions."""
def __init__(self, base_url, headers=None, timeout=30):
"""
Args:
base_url: Base URL for all requests
headers: Default headers for all requests
timeout: Default timeout in seconds
"""
# YOUR CODE HERE
pass
def get(self, endpoint, params=None, **kwargs):
"""Make a GET request."""
pass
def post(self, endpoint, data=None, json_data=None, **kwargs):
"""Make a POST request."""
pass
def put(self, endpoint, data=None, json_data=None, **kwargs):
"""Make a PUT request."""
pass
def delete(self, endpoint, **kwargs):
"""Make a DELETE request."""
pass
def _request(self, method, endpoint, **kwargs):
"""Internal method to make requests."""
pass
# ============================================================
# EXERCISE 8: JSON File Cache
# ============================================================
"""
Create a cache class that stores API responses in JSON files
with expiration support.
Example:
>>> cache = JSONFileCache("./cache", ttl=3600) # 1 hour TTL
>>> cache.set("user_123", {"name": "John"})
>>> cache.get("user_123")
{"name": "John"}
>>> cache.get("expired_key") # Returns None if expired
None
"""
import json
import os
import time
from pathlib import Path
class JSONFileCache:
"""File-based JSON cache with TTL support."""
def __init__(self, cache_dir, ttl=3600):
"""
Args:
cache_dir: Directory to store cache files
ttl: Time-to-live in seconds (default 1 hour)
"""
# YOUR CODE HERE
pass
def _get_cache_path(self, key):
"""Get the file path for a cache key."""
pass
def set(self, key, value):
"""Store a value in the cache."""
pass
def get(self, key):
"""Get a value from the cache (None if missing/expired)."""
pass
def delete(self, key):
"""Delete a value from the cache."""
pass
def clear(self):
"""Clear all cached values."""
pass
# ============================================================
# EXERCISE 9: API Pagination Iterator
# ============================================================
"""
Create a class that iterates through paginated API responses.
Example:
>>> paginator = APIPaginator(
... client,
... "/users",
... per_page=50
... )
>>> for user in paginator:
... print(user['name'])
"""
class APIPaginator:
"""Iterate through paginated API responses."""
def __init__(self, client, endpoint, per_page=10, max_pages=None):
"""
Args:
client: HTTP client instance
endpoint: API endpoint to paginate
per_page: Items per page
max_pages: Maximum pages to fetch (None for all)
"""
# YOUR CODE HERE
pass
def __iter__(self):
"""Return iterator."""
pass
def __next__(self):
"""Get next item."""
pass
def _fetch_page(self, page):
"""Fetch a specific page."""
pass
# ============================================================
# EXERCISE 10: Webhook Handler
# ============================================================
"""
Create a simple webhook handler that validates and processes
incoming webhook payloads.
Example:
>>> handler = WebhookHandler(secret="my-secret")
>>> handler.add_handler("user.created", handle_user_created)
>>> handler.add_handler("order.completed", handle_order_completed)
>>> handler.process(payload, signature)
"""
import hashlib
import hmac
import json
class WebhookHandler:
"""Handle incoming webhooks with validation."""
def __init__(self, secret):
"""
Args:
secret: Secret key for signature validation
"""
# YOUR CODE HERE
pass
def add_handler(self, event_type, handler_func):
"""Register a handler for an event type."""
pass
def validate_signature(self, payload, signature):
"""Validate webhook signature."""
pass
def process(self, payload, signature=None):
"""Process an incoming webhook."""
pass
# ============================================================
# CHALLENGE: Build a REST API Client for JSONPlaceholder
# ============================================================
"""
JSONPlaceholder (https://jsonplaceholder.typicode.com) is a free
fake REST API for testing. Build a complete client for it.
The client should support:
- Posts: list, get, create, update, delete
- Comments: list by post, create
- Users: list, get
- Todos: list by user
Include:
- Proper error handling
- Response caching
- Rate limiting
- Logging
Example usage:
>>> client = JSONPlaceholderClient()
>>> posts = client.posts.list(limit=5)
>>> post = client.posts.get(1)
>>> new_post = client.posts.create(title="Hello", body="World", user_id=1)
>>> client.posts.update(1, title="Updated")
>>> client.posts.delete(1)
>>> comments = client.comments.list_by_post(1)
>>> user = client.users.get(1)
>>> todos = client.todos.list_by_user(1)
"""
class JSONPlaceholderClient:
"""Client for JSONPlaceholder API."""
BASE_URL = "https://jsonplaceholder.typicode.com"
def __init__(self):
# YOUR CODE HERE
pass
# Implement Posts, Comments, Users, Todos resources
# with proper caching, error handling, and rate limiting
# ============================================================
# SOLUTIONS (Uncomment to check your work)
# ============================================================
"""
# Solution 1: URL Parser
def parse_url(url):
parsed = urlparse(url)
port = parsed.port if parsed.port else (443 if parsed.scheme == 'https' else 80)
return {
'scheme': parsed.scheme,
'host': parsed.hostname,
'port': port,
'path': parsed.path,
'query': parse_qs(parsed.query),
'fragment': parsed.fragment
}
# Solution 2: Query String Builder
def build_url(base_url, params):
if not params:
return base_url
query_string = urlencode(params)
return f"{base_url}?{query_string}"
# Solution 3: JSON Data Validator
def validate_json(json_string, required_keys=None):
try:
data = json.loads(json_string)
except json.JSONDecodeError as e:
return (False, f"Invalid JSON: {e}")
if required_keys:
missing = [key for key in required_keys if key not in data]
if missing:
return (False, f"Missing required keys: {missing}")
return (True, data)
# Solution 4: API Response Handler
class APIResponseHandler:
def __init__(self, response):
self.response = response
self._data = None
def is_success(self):
return 200 <= self.response.status_code < 300
def get_status(self):
return self.response.status_code
def get_data(self):
if self._data is None:
try:
self._data = self.response.json()
except:
self._data = None
return self._data
def get_header(self, header_name, default=None):
return self.response.headers.get(header_name, default)
def get_error_message(self):
if self.is_success():
return None
data = self.get_data()
if data and 'error' in data:
return data['error']
if data and 'message' in data:
return data['message']
return f"HTTP {self.get_status()}"
# Solution 5: Rate Limiter
class RateLimiter:
def __init__(self, max_requests, period):
self.max_requests = max_requests
self.period = period
self.requests = deque()
def _clean_old_requests(self):
now = time.time()
while self.requests and now - self.requests[0] > self.period:
self.requests.popleft()
def can_proceed(self):
self._clean_old_requests()
return len(self.requests) < self.max_requests
def record_request(self):
self.requests.append(time.time())
def wait_time(self):
self._clean_old_requests()
if len(self.requests) < self.max_requests:
return 0
oldest = self.requests[0]
return self.period - (time.time() - oldest)
# Solution 6: Retry Decorator
def retry(max_attempts=3, backoff_factor=2, exceptions=(Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
delay = 1
last_exception = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exception = e
if attempt < max_attempts - 1:
time.sleep(delay)
delay *= backoff_factor
raise last_exception
return wrapper
return decorator
# Solution 7: Simple HTTP Client
import requests
class SimpleHTTPClient:
def __init__(self, base_url, headers=None, timeout=30):
self.base_url = base_url.rstrip('/')
self.timeout = timeout
self.session = requests.Session()
if headers:
self.session.headers.update(headers)
def _request(self, method, endpoint, **kwargs):
url = f"{self.base_url}/{endpoint.lstrip('/')}"
kwargs.setdefault('timeout', self.timeout)
response = self.session.request(method, url, **kwargs)
response.raise_for_status()
return response.json()
def get(self, endpoint, params=None, **kwargs):
return self._request('GET', endpoint, params=params, **kwargs)
def post(self, endpoint, data=None, json_data=None, **kwargs):
return self._request('POST', endpoint, data=data, json=json_data, **kwargs)
def put(self, endpoint, data=None, json_data=None, **kwargs):
return self._request('PUT', endpoint, data=data, json=json_data, **kwargs)
def delete(self, endpoint, **kwargs):
return self._request('DELETE', endpoint, **kwargs)
# Solution 8: JSON File Cache
class JSONFileCache:
def __init__(self, cache_dir, ttl=3600):
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(parents=True, exist_ok=True)
self.ttl = ttl
def _get_cache_path(self, key):
safe_key = key.replace('/', '_').replace('\\\\', '_')
return self.cache_dir / f"{safe_key}.json"
def set(self, key, value):
cache_data = {
'data': value,
'timestamp': time.time()
}
with open(self._get_cache_path(key), 'w') as f:
json.dump(cache_data, f)
def get(self, key):
path = self._get_cache_path(key)
if not path.exists():
return None
with open(path, 'r') as f:
cache_data = json.load(f)
if time.time() - cache_data['timestamp'] > self.ttl:
path.unlink()
return None
return cache_data['data']
def delete(self, key):
path = self._get_cache_path(key)
if path.exists():
path.unlink()
def clear(self):
for path in self.cache_dir.glob('*.json'):
path.unlink()
"""
if __name__ == "__main__":
print("Networking & APIs Exercises")
print("=" * 50)
print("\nComplete the exercises above to practice:")
print("- URL parsing and building")
print("- JSON validation")
print("- API response handling")
print("- Rate limiting")
print("- Retry patterns")
print("- HTTP client design")
print("- Caching strategies")
print("- Pagination handling")
print("- Webhook processing")
print("\nUncomment the solutions to check your work!")