diff --git a/docs/upload.md b/docs/upload.md index b96e25f..1249b2a 100644 --- a/docs/upload.md +++ b/docs/upload.md @@ -349,13 +349,14 @@ To force a fresh upload (ignoring any existing state), simply delete the state f The SDK raises specific exceptions for common error cases: -| Exception | Cause | -|-----------|-------| -| `FileNotFoundError` | The specified file path does not exist | -| `ValidationError` | Invalid `DatasetSubmission` or required string inputs | -| `ValueError` | Missing or invalid required parameter | -| `PermissionError` | API key is invalid or lacks permissions | -| `RuntimeError` | Rate limit exceeded or upload failed | +| Exception | Cause | +|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `FileNotFoundError` | The specified file path does not exist | +| `ValidationError` | Invalid `DatasetSubmission` or required string inputs | +| `ValueError` | Missing or invalid required parameter | +| `AuthenticationError` | The API key is invalid, expired or revoked | +| `PermissionError` | When downloading: You have not agreed to the T&C of the dataset. When uploading: your organization is not approved to upload datasets, or the API key was created before the approval was granted | +| `RuntimeError` | Rate limit exceeded or upload failed | ## Using the DatasetSubmission Model diff --git a/src/datacollective/__init__.py b/src/datacollective/__init__.py index 755d18f..54f0297 100644 --- a/src/datacollective/__init__.py +++ b/src/datacollective/__init__.py @@ -6,6 +6,10 @@ load_dataset, save_dataset_to_disk, ) +from datacollective.errors import ( + AuthenticationError, + RateLimitError, +) from datacollective.models import ( DatasetDetails, DatasetSubmission, @@ -36,6 +40,8 @@ "License", "Task", "Visibility", + "AuthenticationError", + "RateLimitError", "__version__", ] diff --git a/src/datacollective/api_utils.py b/src/datacollective/api_utils.py index 57bb800..47d3101 100644 --- a/src/datacollective/api_utils.py +++ b/src/datacollective/api_utils.py @@ -60,6 +60,7 @@ def _send_api_request( Raises: FileNotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is not accepted (401). PermissionError: If access is denied (403). RateLimitError: If rate limit is exceeded (429). ValueError: If API key is missing when authentication is required. @@ -95,12 +96,23 @@ def _send_api_request( f"Resource not found: {method.upper()} {url}" + (f" — {detail}" if detail else "") ) + if resp.status_code == 401: + from datacollective.errors import AuthenticationError + + detail = _extract_error_detail(resp) + raise AuthenticationError( + f"Authentication failed. The API key in `{ENV_API_KEY}` is invalid, expired or revoked." + " Create a new key on the MDC platform (https://mozilladatacollective.com/api-reference)." + + (f"\n{detail}" if detail else "") + ) if resp.status_code == 403: detail = _extract_error_detail(resp) raise PermissionError( - f"Access denied. If the dataset is public, make sure you have read thoroughly and agreed" - " to the dataset's Terms & Conditions in its respective page on the MDC platform before downloading. \n" - f"{detail}" + "Access denied. When downloading, make sure you have read thoroughly and agreed to the dataset's" + " Terms & Conditions in its respective page on the MDC platform before downloading." + "When uploading, this means your organization is not approved to upload" + " datasets yet, or the API key was created before the approval was granted." + + (f"\n{detail}" if detail else "") ) if resp.status_code == 429: from datacollective.errors import RateLimitError diff --git a/src/datacollective/errors.py b/src/datacollective/errors.py index 48be1c9..609a1f9 100644 --- a/src/datacollective/errors.py +++ b/src/datacollective/errors.py @@ -46,6 +46,10 @@ class MissingDependencyError(ImportError): """Raised when an optional dependency required for a feature is not installed.""" +class AuthenticationError(RuntimeError): + """Raised when the MDC API responds with HTTP 401.""" + + class RateLimitError(RuntimeError): """Raised when the MDC API responds with HTTP 429."""