mager-bench

GPT-OSS 120B × api-client

3.7

Class design + error handling + type hints + docs

correctness 1.0quality 4.0documentation 6.03251ms
$ 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

3.7
correctness 1.0quality 4.0documentation 6.03251ms

# judge: Response is truncated mid-code in the usage example, making the file syntactically invalid and non-runnable despite otherwise solid class design.

$ cat gpt-oss-120b.api-client.r1.response
```python
"""
api_client.py

A tiny wrapper around the :pypi:`requests` library that simplifies interaction with a
REST‑style HTTP API.  It automatically prefixes all request URLs with a base URL,
adds an optional ``Authorization`` header, and raises a rich ``APIError`` for
non‑successful HTTP responses.

Typical usage
-------------
>>> client = APIClient("https://api.example.com/v1", api_key="my‑secret‑token")
>>> response = client.get("/users", params={"active": True})
>>> print(response)                     # ← decoded JSON payload
>>> client.post("/items", data={"name": "Widget", "price": 12.5})
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, Optional, Union

import requests


class APIError(RuntimeError):
    """
    Exception raised when the remote API returns a non‑2xx HTTP status code.

    Attributes
    ----------
    status_code: int
        The HTTP status code returned by the server.
    message: str
        The error message – either the response body (if JSON) or the raw text.
    response: requests.Response
        The original :class:`requests.Response` object for further inspection.
    """

    def __init__(self, status_code: int, message: str, response: requests.Response) -> None:
        super().__init__(f"API request failed with status {status_code}: {message}")
        self.status_code: int = status_code
        self.message: str = message
        self.response: requests.Response = response


class APIClient:
    """
    Simple HTTP client for a JSON‑based REST API.

    Parameters
    ----------
    base_url : str
        The root URL for all API requests (e.g. ``https://api.example.com/v1``).
    api_key : str, optional
        If supplied, an ``Authorization: Bearer <api_key>`` header is added to every
        request.  The header name/value can be customised by subclassing.

    Notes
    -----
    The client expects that the API returns JSON bodies for successful responses.
    All responses are automatically decoded with ``response.json()`` and the
    resulting Python object (typically a ``dict`` or ``list``) is returned.
    """

    def __init__(self, base_url: str, api_key: Optional[str] = None) -> None:
        self.base_url: str = base_url.rstrip("/")          # normalise trailing slash
        self.api_key: Optional[str] = api_key
        self.session: requests.Session = requests.Session()
        self.session.headers.update({"Accept": "application/json"})
        if api_key:
            self.session.headers.update({"Authorization": f"Bearer {api_key}"})

    def _build_url(self, path: str) -> str:
        """Join ``base_url`` and ``path`` ensuring exactly one ``/`` separator."""
        return f"{self.base_url}/{path.lstrip('/')}"

    def _handle_response(self, response: requests.Response) -> Any:
        """
        Validate *response* and return the decoded JSON payload.

        Raises
        ------
        APIError
            If the HTTP status code is not in the ``200‑299`` range.
        """
        if not response.ok:
            # Try to pull a helpful message from the JSON body; fall back to raw text.
            try:
                error_info = response.json()
                msg = error_info.get("message") or error_info.get("error") or str(error_info)
            except ValueError:  # response not JSON
                msg = response.text.strip() or response.reason
            raise APIError(response.status_code, msg, response)

        # Successful response – decode JSON (or return raw text if not JSON)
        try:
            return response.json()
        except ValueError:
            return response.text

    def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
        """
        Perform an HTTP ``GET`` request.

        Parameters
        ----------
        path : str
            Relative API path (e.g. ``"/users"``).  Leading ``/`` is optional.
        params : dict, optional
            Query‑string parameters to be URL‑encoded.

        Returns
        -------
        Any
            The decoded JSON payload (or raw text if the response is not JSON).

        Raises
        ------
        APIError
            If the server returns a non‑2xx status code.
        """
        url = self._build_url(path)
        response = self.session.get(url, params=params, timeout=30)
        return self._handle_response(response)

    def post(self, path: str, data: Union[Dict[str, Any], Any]) -> Any:
        """
        Perform an HTTP ``POST`` request with a JSON body.

        Parameters
        ----------
        path : str
            Relative API path (e.g. ``"/items"``).  Leading ``/`` is optional.
        data : dict or any JSON‑serialisable object
            The payload to be sent as ``application/json``.

        Returns
        -------
        Any
            The decoded JSON payload (or raw text if the response is not JSON).

        Raises
        ------
        APIError
            If the server returns a non‑2xx status code.
        """
        url = self._build_url(path)
        response = self.session.post(url, json=data, timeout=30)
        return self._handle_response(response)


# --------------------------------------------------------------------------- #
# Example usage (run this module directly to see a quick demo)
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
    # NOTE: Replace the URL and API key with real values for an actual test.
    client = APIClient("https://jsonplaceholder.typicode.com", api_key=None)

    # GET request example – fetch a public post
    try:
        post = client.get("/posts/1")
        print("GET /posts/1 →", post)
    except APIError as exc:
        print(f"GET request failed: {exc}")

    # POST request example – create a new post (the placeholder API echoes it back)
    try:
        new_post = client.post(
            "/posts",
            data={"title": "Foo", "body": "Bar", "userId": 1},
        )
        print("POST /posts →", new_post)
    except APIError as exc:
        print(f"POST request failed: {exc}")
```