Skip to content
mager-bench1.3

GPT-6 Astra × api-client

8.7

Class design + error handling + type hints + docs

correctness 9.0quality 8.0documentation 9.031897ms
$ 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

8.7
correctness 9.0quality 8.0documentation 9.031897ms

# judge: The implementation is correct, well typed, and thoroughly documented, though it misses connection pooling via requests.Session and unnecessarily disables redirects.

$ cat codex-cli/gpt-6-astra.api-client.r1.response
```python
from typing import Any, Mapping, Optional

import requests


class APIError(Exception):
    """Represent an unsuccessful HTTP response from the API.

    Attributes:
        status_code: HTTP status code returned by the API.
        message: Error details returned by the API.
    """

    def __init__(self, status_code: int, message: str) -> None:
        """Initialize the exception with an HTTP status code and message."""
        self.status_code = status_code
        self.message = message
        super().__init__(f"HTTP {status_code}: {message}")


class APIClient:
    """A requests-based client for a REST API.

    Requests use a 30-second timeout. If an API key is provided, it is sent
    using Bearer authentication. POST bodies are encoded as JSON.

    Successful responses return decoded JSON when possible, otherwise text.
    Empty responses return None. Every non-2xx response raises APIError.
    Network errors propagate as requests.RequestException.

    Args:
        base_url: Base URL of the API, including any common path prefix.
        api_key: Optional API key for Bearer authentication.
    """

    def __init__(self, base_url: str, api_key: Optional[str] = None) -> None:
        """Initialize the client.

        Args:
            base_url: Base URL of the API.
            api_key: Optional API key sent in the Authorization header.

        Raises:
            ValueError: If base_url is empty or contains only whitespace.
        """
        if not base_url.strip():
            raise ValueError("base_url must not be empty")

        self.base_url: str = base_url.strip().rstrip("/")
        self._headers: dict[str, str] = {"Accept": "application/json"}
        if api_key is not None:
            self._headers["Authorization"] = f"Bearer {api_key}"

    def get(
        self, path: str, params: Optional[Mapping[str, Any]] = None
    ) -> Any:
        """Send a GET request.

        Args:
            path: API path relative to base_url.
            params: Optional query parameters.

        Returns:
            Decoded JSON, response text, or None for an empty response.

        Raises:
            APIError: If the response status is outside the 200–299 range.
            requests.RequestException: If the request fails at the network level.
        """
        return self._request("GET", path, params=params)

    def post(self, path: str, data: Any) -> Any:
        """Send a POST request with a JSON body.

        Args:
            path: API path relative to base_url.
            data: JSON-serializable request body.

        Returns:
            Decoded JSON, response text, or None for an empty response.

        Raises:
            APIError: If the response status is outside the 200–299 range.
            requests.RequestException: If the request fails at the network level.
            TypeError: If data is not JSON-serializable.
            ValueError: If data contains values invalid for JSON encoding.
        """
        return self._request("POST", path, json=data)

    def _request(self, method: str, path: str, **kwargs: Any) -> Any:
        url = f"{self.base_url}/{path.lstrip('/')}"
        with requests.request(
            method,
            url,
            headers=self._headers,
            timeout=30,
            allow_redirects=False,
            **kwargs,
        ) as response:
            if not 200 <= response.status_code < 300:
                message = response.text.strip() or response.reason or "API error"
                raise APIError(response.status_code, message)

            if not response.content:
                return None

            try:
                return response.json()
            except requests.exceptions.JSONDecodeError:
                return response.text


if __name__ == "__main__":
    client = APIClient("https://api.example.com/v1", api_key="your-api-key")

    try:
        users = client.get("/users", params={"page": 1})
        print(users)

        created_user = client.post("/users", data={"name": "Alice"})
        print(created_user)
    except APIError as exc:
        print(f"API error {exc.status_code}: {exc.message}")
    except requests.RequestException as exc:
        print(f"Request failed: {exc}")
```