GPT-OSS 120B × api-client
3.7Class 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.7correctness 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}")
```