GPT-5.6 Sol × api-client
8.7Class design + error handling + type hints + docs
correctness 9.0quality 8.0documentation 9.014062ms
$ 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.7correctness 9.0quality 8.0documentation 9.014062ms
# judge: The implementation cleanly satisfies the core requirements with proper error handling, typing, sessions, and docstrings, though it lacks timeout/session-closing support and the example may fail against the placeholder URL without catching transport errors.
$ cat codex-cli/gpt-5.6-sol.api-client.r1.response
```python
from typing import Any, Mapping, Optional
import requests
class APIError(Exception):
"""Raised when the API returns a non-successful HTTP response."""
def __init__(self, status_code: int, message: str) -> None:
"""Initialize the error with an HTTP status code and message."""
self.status_code = status_code
self.message = message
super().__init__(f"API request failed ({status_code}): {message}")
class APIClient:
"""A lightweight client for making authenticated REST API requests.
Args:
base_url: The root URL of the API, such as
``"https://api.example.com/v1"``.
api_key: Optional bearer token used to authenticate requests.
"""
def __init__(self, base_url: str, api_key: Optional[str] = None) -> None:
"""Initialize the API client.
Args:
base_url: The root URL of the API.
api_key: Optional bearer token used for authentication.
"""
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
if api_key is not None:
self.session.headers["Authorization"] = f"Bearer {api_key}"
self.session.headers["Accept"] = "application/json"
def get(
self,
path: str,
params: Optional[Mapping[str, Any]] = None,
) -> Any:
"""Send a GET request to an API endpoint.
Args:
path: Endpoint path relative to the base URL.
params: Optional query-string parameters.
Returns:
The decoded JSON response.
Raises:
APIError: If the API returns a non-2xx response.
requests.RequestException: If the request cannot be completed.
requests.JSONDecodeError: If the response is not valid JSON.
"""
response = self.session.get(self._url(path), params=params)
self._raise_for_api_error(response)
return response.json()
def post(self, path: str, data: Mapping[str, Any]) -> Any:
"""Send a POST request containing JSON data to an API endpoint.
Args:
path: Endpoint path relative to the base URL.
data: Mapping to serialize as the JSON request body.
Returns:
The decoded JSON response.
Raises:
APIError: If the API returns a non-2xx response.
requests.RequestException: If the request cannot be completed.
requests.JSONDecodeError: If the response is not valid JSON.
"""
response = self.session.post(self._url(path), json=data)
self._raise_for_api_error(response)
return response.json()
def _url(self, path: str) -> str:
return f"{self.base_url}/{path.lstrip('/')}"
@staticmethod
def _raise_for_api_error(response: requests.Response) -> None:
if 200 <= response.status_code < 300:
return
try:
payload = response.json()
message = (
payload.get("message", response.text)
if isinstance(payload, dict)
else response.text
)
except requests.exceptions.JSONDecodeError:
message = response.text
raise APIError(response.status_code, message or response.reason)
# Usage example
if __name__ == "__main__":
client = APIClient(
base_url="https://api.example.com/v1",
api_key="your-api-key",
)
try:
users = client.get("/users", params={"limit": 10})
created_user = client.post(
"/users",
data={"name": "Ada Lovelace", "email": "ada@example.com"},
)
print(users)
print(created_user)
except APIError as error:
print(f"Request failed: {error}")
```