Claude Haiku 4.5 × api-client
1.7Class design + error handling + type hints + docs
correctness 0.0quality 2.0documentation 3.010799ms
$ 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.010799ms
# judge: The response is truncated mid-code (post method incomplete, code cuts off at 'headers = s'), leaving the class non-functional and lacking the usage example entirely.
$ cat claude-haiku-4-5.api-client.r1.response
```python
"""
A simple REST API client wrapper around the requests library.
"""
import requests
from typing import Any, Optional, Dict
from requests.exceptions import RequestException
class APIError(Exception):
"""
Custom exception raised when an API request fails.
Attributes:
status_code (int): The HTTP status code of the failed response.
message (str): The error message from the API response.
"""
def __init__(self, status_code: int, message: str) -> None:
"""
Initialize the APIError.
Args:
status_code: The HTTP status code.
message: The error message.
"""
self.status_code = status_code
self.message = message
super().__init__(f"API Error {status_code}: {message}")
class APIClient:
"""
A wrapper around the requests library for making REST API calls.
This client provides a simple interface for making GET and POST requests
to a REST API with optional authentication via API key. It automatically
handles error responses by raising an APIError exception.
Attributes:
base_url (str): The base URL for all API requests.
api_key (Optional[str]): Optional API key for authentication.
"""
def __init__(self, base_url: str, api_key: Optional[str] = None) -> None:
"""
Initialize the APIClient.
Args:
base_url: The base URL for the API (e.g., "https://api.example.com/v1").
api_key: Optional API key for authentication. If provided, it will be
included in the Authorization header as a Bearer token.
Raises:
ValueError: If base_url is empty.
"""
if not base_url:
raise ValueError("base_url cannot be empty")
self.base_url = base_url.rstrip("/")
self.api_key = api_key
def _get_headers(self) -> Dict[str, str]:
"""
Get headers for API requests.
Returns:
A dictionary of headers including the Authorization header if
an API key is configured.
"""
headers: Dict[str, str] = {
"Content-Type": "application/json",
}
if self.api_key:
headers["Authorization"] = f"Bearer {self.api_key}"
return headers
def _build_url(self, path: str) -> str:
"""
Build the full URL from base_url and path.
Args:
path: The API endpoint path (e.g., "/users" or "users/123").
Returns:
The full URL.
"""
# Remove leading slash from path to avoid double slashes
path = path.lstrip("/")
return f"{self.base_url}/{path}"
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
"""
Handle the API response and raise APIError on non-2xx status codes.
Args:
response: The requests Response object.
Returns:
The JSON-decoded response body.
Raises:
APIError: If the response status code is not in the 2xx range.
"""
if not response.ok:
# Try to extract error message from JSON response
try:
error_data = response.json()
message = error_data.get("message") or error_data.get("error") or response.text
except (ValueError, KeyError):
message = response.text or f"HTTP {response.status_code}"
raise APIError(response.status_code, message)
# Try to parse JSON, return empty dict if no content
if response.content:
try:
return response.json()
except ValueError:
return {"raw_response": response.text}
return {}
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 query parameters as a dictionary.
Returns:
The JSON-decoded response body as a dictionary.
Raises:
APIError: If the response status code is not in the 2xx range.
RequestException: If the request fails due to network issues.
Example:
>>> client = APIClient("https://api.example.com/v1", api_key="secret")
>>> response = client.get("/users", params={"page": 1, "limit": 10})
"""
url = self._build_url(path)
headers = self._get_headers()
try:
response = requests.get(url, params=params, headers=headers)
return self._handle_response(response)
except RequestException as e:
raise APIError(0, f"Network request failed: {str(e)}")
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: The request body data as a dictionary. This will be
JSON-encoded and sent with the appropriate Content-Type header.
Returns:
The JSON-decoded response body as a dictionary.
Raises:
APIError: If the response status code is not in the 2xx range.
RequestException: If the request fails due to network issues.
Example:
>>> client = APIClient("https://api.example.com/v1", api_key="secret")
>>> response = client.post("/users", data={"name": "John", "email": "john@example.com"})
"""
url = self._build_url(path)
headers = self._get_headers()
try:
response = requests.post(url, json=data, headers=headers)
return self._handle_response(response)
except RequestException as e:
raise APIError(0, f"Network request failed: {str(e)}")
# ============================================================================
# USAGE EXAMPLE
# ============================================================================
if __name__ == "__main__":
# Example 1: Create a client without authentication
client = APIClient("https://jsonplaceholder.typicode.com")
try:
# GET request example
print("=== GET Request Example ===")
posts = client.get("/posts", params={"userId": 1, "_limit": 2})
print(f"Retrieved {len(posts)} posts:")
for post in posts:
print(f" - {post.get('title')}")
# POST request example
print("\n=== POST Request Example ===")
new_post = client.post("/posts", data={
"title": "My New Post",
"body": "This is the content of my post",
"userId": 1
})
print(f"Created post with ID: {new_post.get('id')}")
print(f"Title: {new_post.get('title')}")
except APIError as e:
print(f"API Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
# Example 2: Demonstrating error handling
print("\n=== Error Handling Example ===")
try:
# This will fail because the endpoint doesn't exist
response = client.get("/nonexistent-endpoint")
except APIError as e:
print(f"Caught APIError: Status {e.status_code}")
print(f"Message: {e.message}")
# Example 3: Client with API key (though not actually used in this example)
print("\n=== Client with API Key ===")
secure_client = APIClient(
base_url="https://api.example.com/v1",
api_key="your-secret-api-key