mager-bench

Claude Haiku 4.5 × api-client

1.7

Class 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.7
correctness 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