|
10 | 10 |
|
11 | 11 | from .utils import AutoPrettyPrint |
12 | 12 |
|
| 13 | + |
| 14 | +class DatalabAPIError(Exception): |
| 15 | + """Base exception for Datalab API errors.""" |
| 16 | + |
| 17 | + |
| 18 | +class DuplicateItemError(DatalabAPIError): |
| 19 | + """Raised when the API operation would create a duplicate item.""" |
| 20 | + |
13 | 21 | __version__ = version("datalab-api") |
14 | 22 |
|
15 | 23 | __all__ = ("BaseDatalabClient", "__version__") |
@@ -50,7 +58,7 @@ def __init__(self, datalab_api_url: str, log_level: str = "WARNING"): |
50 | 58 |
|
51 | 59 | Parameters: |
52 | 60 | datalab_api_url: The URL of the Datalab API. |
53 | | - TODO: If the URL of a datalab *UI* is provided, a request will be made to attempt |
| 61 | + If the URL of a datalab *UI* is provided, a request will be made to attempt |
54 | 62 | to resolve the underlying API URL (e.g., `https://demo-api.datalab-org.io` |
55 | 63 | will 'redirect' to `https://demo-api.datalab-org.io`). |
56 | 64 | log_level: The logging level to use for the client. Defaults to "WARNING". |
@@ -204,3 +212,97 @@ def __enter__(self): |
204 | 212 | def __exit__(self, *_): |
205 | 213 | if self._session is not None: |
206 | 214 | self._session.close() |
| 215 | + |
| 216 | + def _handle_response( |
| 217 | + self, response: httpx.Response, url: str, expected_status: int = 200 |
| 218 | + ) -> dict[str, Any]: |
| 219 | + """Handle HTTP response with consistent error handling. |
| 220 | +
|
| 221 | + Args: |
| 222 | + response: The HTTP response object |
| 223 | + url: The URL that was requested |
| 224 | + expected_status: The expected HTTP status code (default: 200) |
| 225 | +
|
| 226 | + Returns: |
| 227 | + The JSON response data |
| 228 | +
|
| 229 | + Raises: |
| 230 | + DuplicateItemError: For 409 conflicts or duplicate key errors |
| 231 | + DatalabAPIError: For other API errors |
| 232 | + """ |
| 233 | + # Handle HTTP status code errors |
| 234 | + if response.status_code != expected_status: |
| 235 | + try: |
| 236 | + error_data = response.json() |
| 237 | + error_message = error_data.get("message", str(response.content)) |
| 238 | + |
| 239 | + # Handle specific error cases |
| 240 | + if response.status_code == 409: |
| 241 | + raise DuplicateItemError(f"Duplicate item error at {url}: {error_message}") |
| 242 | + |
| 243 | + raise DatalabAPIError( |
| 244 | + f"HTTP {response.status_code} error at {url}: {error_message}" |
| 245 | + ) |
| 246 | + except ValueError: |
| 247 | + # Response is not JSON |
| 248 | + raise DatalabAPIError( |
| 249 | + f"HTTP {response.status_code} error at {url}: {response.content}" |
| 250 | + ) |
| 251 | + |
| 252 | + # Parse JSON response |
| 253 | + try: |
| 254 | + data = response.json() |
| 255 | + except ValueError as e: |
| 256 | + raise DatalabAPIError(f"Invalid JSON response from {url}: {e}") |
| 257 | + |
| 258 | + # Handle API-level status errors |
| 259 | + if isinstance(data, dict) and data.get("status") != "success": |
| 260 | + error_message = data.get("message", data.get("status", "Unknown error")) |
| 261 | + |
| 262 | + # Check for duplicate key errors in the message |
| 263 | + if "DuplicateKeyError" in error_message: |
| 264 | + raise DuplicateItemError(f"Duplicate item error at {url}: {error_message}") |
| 265 | + |
| 266 | + raise DatalabAPIError(f"API error at {url}: {error_message}") |
| 267 | + |
| 268 | + return data |
| 269 | + |
| 270 | + def _request( |
| 271 | + self, method: str, url: str, expected_status: int = 200, **kwargs |
| 272 | + ) -> dict[str, Any]: |
| 273 | + """Make an HTTP request with consistent error handling. |
| 274 | +
|
| 275 | + Args: |
| 276 | + method: HTTP method (GET, POST, PUT, etc.) |
| 277 | + url: The URL to request |
| 278 | + expected_status: The expected HTTP status code (default: 200) |
| 279 | + **kwargs: Additional arguments to pass to the request |
| 280 | +
|
| 281 | + Returns: |
| 282 | + The JSON response data |
| 283 | + """ |
| 284 | + try: |
| 285 | + response = self.session.request(method, url, follow_redirects=True, **kwargs) |
| 286 | + return self._handle_response(response, url, expected_status) |
| 287 | + except (httpx.RequestError, httpx.HTTPStatusError) as e: |
| 288 | + raise DatalabAPIError(f"Request failed for {url}: {e}") |
| 289 | + |
| 290 | + def _get(self, url: str, **kwargs) -> dict[str, Any]: |
| 291 | + """Make a GET request with error handling.""" |
| 292 | + return self._request("GET", url, **kwargs) |
| 293 | + |
| 294 | + def _post(self, url: str, expected_status: int = 200, **kwargs) -> dict[str, Any]: |
| 295 | + """Make a POST request with error handling.""" |
| 296 | + return self._request("POST", url, expected_status, **kwargs) |
| 297 | + |
| 298 | + def _put(self, url: str, expected_status: int = 201, **kwargs) -> dict[str, Any]: |
| 299 | + """Make a PUT request with error handling.""" |
| 300 | + return self._request("PUT", url, expected_status, **kwargs) |
| 301 | + |
| 302 | + def _patch(self, url: str, **kwargs) -> dict[str, Any]: |
| 303 | + """Make a PATCH request with error handling.""" |
| 304 | + return self._request("PATCH", url, **kwargs) |
| 305 | + |
| 306 | + def _delete(self, url: str, expected_status: int = 200, **kwargs) -> dict[str, Any]: |
| 307 | + """Make a DELETE request with error handling.""" |
| 308 | + return self._request("DELETE", url, expected_status, **kwargs) |
0 commit comments