Claude Haiku 4.5 × api-client
1.7Class design + error handling + type hints + docs
correctness 0.0quality 2.0documentation 3.014221ms
$ cat api-client.prompt — what the model was asked
Write a Python class `APIClient` that wraps the `requests` library for a REST API. It should: (1) accept a base_url and optional api_key in __init__, (2) have a `get(path, params=None)` method and a `post(path, data)` method, (3) raise a custom `APIError` exception with the status code and message on non-2xx responses, (4) include type hints throughout, (5) have a complete docstring on the class and each public method. Show a usage example at the bottom.
run 1 of 1
1.7correctness 0.0quality 2.0documentation 3.014221ms
# judge: Response is truncated mid-line inside the post() method, leaving the code non-functional and missing the required usage example entirely.
$ cat claude-haiku-4-5.api-client.r1.response
```python
"""
REST API Client Module
This module provides a simple wrapper around the requests library for
interacting with REST APIs with built-in error handling and authentication.
"""
import requests
from typing import Any, Optional, Dict
from requests.exceptions import RequestException, Timeout, ConnectionError
class APIError(Exception):
"""
Custom exception raised when an API request fails.
Attributes:
status_code (int): HTTP status code of the failed response
message (str): Error message describing the failure
"""
def __init__(self, status_code: int, message: str) -> None:
"""
Initialize the APIError exception.
Args:
status_code: HTTP status code from the failed response
message: Descriptive error message
"""
self.status_code = status_code
self.message = message
super().__init__(f"API Error {status_code}: {message}")
class APIClient:
"""
A lightweight REST API client wrapper around the requests library.
This class provides methods for making GET and POST requests to a REST API
with automatic error handling, optional API key authentication, and support
for query parameters and request bodies.
Attributes:
base_url (str): The base URL of the API
api_key (str): Optional API key for authentication
timeout (int): Request timeout in seconds (default: 10)
Example:
>>> client = APIClient("https://api.example.com", api_key="secret-key")
>>> response = client.get("/users", params={"page": 1})
>>> new_user = client.post("/users", data={"name": "John", "email": "john@example.com"})
"""
def __init__(
self,
base_url: str,
api_key: Optional[str] = None,
timeout: int = 10
) -> None:
"""
Initialize the APIClient with a base URL and optional API key.
Args:
base_url: The base URL of the API (e.g., "https://api.example.com")
api_key: Optional API key for authentication. If provided, it will be
included in the Authorization header as a Bearer token.
timeout: Request timeout in seconds (default: 10)
Raises:
ValueError: If base_url is empty or None
"""
if not base_url:
raise ValueError("base_url cannot be empty")
self.base_url = base_url.rstrip("/") # Remove trailing slash if present
self.api_key = api_key
self.timeout = timeout
def _get_headers(self) -> Dict[str, str]:
"""
Build headers dictionary including authentication if API key is set.
Returns:
Dictionary of HTTP headers including Content-Type and optional Authorization
"""
headers: Dict[str, str] = {
"Content-Type": "application/json",
}
if self.api_key:
headers["Authorization"] = f"Bearer {self.api_key}"
return headers
def _raise_for_status(self, response: requests.Response) -> None:
"""
Check response status and raise APIError for non-2xx responses.
Args:
response: The requests Response object to check
Raises:
APIError: If the response status code is not in the 2xx range
"""
if not response.ok:
try:
error_message = response.json().get(
"message",
response.text or "Unknown error"
)
except (ValueError, AttributeError):
error_message = response.text or "Unknown error"
raise APIError(response.status_code, error_message)
def get(
self,
path: str,
params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Make a GET request to the API.
Args:
path: The API endpoint path (e.g., "/users" or "/users/123")
params: Optional dictionary of query parameters to include in the request
Returns:
The JSON response as a dictionary
Raises:
APIError: If the response status code is not 2xx
Timeout: If the request times out
ConnectionError: If there's a connection error
Example:
>>> client = APIClient("https://api.example.com")
>>> users = client.get("/users", params={"page": 1, "limit": 10})
"""
url = f"{self.base_url}{path}"
try:
response = requests.get(
url,
params=params,
headers=self._get_headers(),
timeout=self.timeout
)
except (Timeout, ConnectionError) as e:
raise
self._raise_for_status(response)
return response.json()
def post(
self,
path: str,
data: Dict[str, Any]
) -> Dict[str, Any]:
"""
Make a POST request to the API.
Args:
path: The API endpoint path (e.g., "/users")
data: Dictionary of data to send in the request body as JSON
Returns:
The JSON response as a dictionary
Raises:
APIError: If the response status code is not 2xx
Timeout: If the request times out
ConnectionError: If there's a connection error
Example:
>>> client = APIClient("https://api.example.com")
>>> new_user = client.post("/users", data={"name": "Alice", "email": "alice@example.com"})
"""
url = f"{self.base_url}{path}"
try:
response = requests.post(
url,
json=data,
headers=self._get_headers(),
timeout=self.timeout
)
except (Timeout, ConnectionError) as e:
raise
self._raise_for_status(response)
return response.json()
# ============================================================================
# Usage Examples
# ============================================================================
if __name__ == "__main__":
"""
Demonstration of APIClient usage with JSONPlaceholder API
(a free fake REST API for testing)
"""
# Initialize client without API key
client = APIClient("https://jsonplaceholder.typicode.com")
print("=" * 60)
print("GET Request Example")
print("=" * 60)
try:
# Fetch a single post
post = client.get("/posts/1")
print(f"Post ID: {post['id']}")
print(f"Title: {post['title']}")
print(f"Body: {post['body'][:100]}...")
except APIError as e:
print(f"API Error: {e}")
except Exception as e:
print(f"Error: {e}")
print("\n" + "=" * 60)
print("GET Request with Query Parameters")
print("=" * 60)
try:
# Fetch posts with query parameters
posts = client.get("/posts", params={"userId": 1, "_limit": 2})
print(f"Found {len(posts)} posts:")
for post in posts:
print(f" - {post['title']}")
except APIError as e:
print(f"API Error: {e}")
except Exception as e:
print(f"Error: {e}")
print("\n" + "=" * 60)
print("POST Request Example")
print("=" * 60)
try:
# Create a new post
new_post = client.post(
"/posts",
data={
"title": "My New Post",
"body": "This is the content of my new post",
"userId": 1
}
)
print(f"Created post with ID: {new_post['id']}")
print(f"Title: {new_post['title']}")
except APIError as e:
print(f"API Error: {e}")
except Exception as e:
print(f"Error: {e}")
print("\n" + "=" * 60)
print("Error Handling Example")
print("=" * 60)
try:
# Request non-existent resource (will get 404)
client.get("/posts/99999")
except APIError as e:
print(f"Caught expected error:")
print(f" Status Code: {e.status_code}")
print(f" Message: {e.message}")
except Exception as e:
print(f"Error: {e}")
print("\n" + "=" * 60)
print("Client with API Key Example")
print("=" * 60)
# Initialize client with API key
secure_client = APIClient(
"https://api.example.com",
api_key="my-secret-api-key"
)
print(f"Client initialized with base_url: {secure_client.base_url}")
print(f"API key authentication: {'Enabled' if secure_client.api_key else 'Disabled'}")
print(f"Headers: {secure_client._get_headers()}")
```
## Key Features Explained:
### 1. **Custom Exception Class**
```python
class APIError(Exception):
"""Stores status_code and message for detailed error handling"""
```
### 2. **Type Hints Throughout**
- All parameters and return types are annotated
- Uses `Optional`, `Dict`, and `Any` from the `typing` module
### 3. **Methods**
- `__init__`: Initializes with base_url and optional api_key
- `get()`: Makes GET requests with optional query parameters
- `post()`: Makes POST requests with JSON body
- `_get_headers()`: Private method to build headers with auth
- `_raise_for_status()`: Private method to check and raise errors
### 4. **Error Handling**
- Raises `APIError` with status code and message on non-2xx responses
- Handles JSON and plain text error responses
- Propagates network errors (Timeout, ConnectionError)
### 5. **Complete Docstrings**
- Module-level docstring
- Class docstring with description and examples
- Method docstrings with Args, Returns, Raises, and Example sections
### 6. **Usage Examples**
The script demonstrates:
- GET requests with and without parameters
- POST requests with data
- Error handling
- Client initialization with API key