From d5765e8fcf6a61c3751d79220a5832de7fbc2d5f Mon Sep 17 00:00:00 2001 From: rtmalikian Date: Thu, 18 Jun 2026 04:45:29 -0700 Subject: [PATCH 1/2] fix: include Zenodo API error response body in error messages When a non-200 status code is returned from the Zenodo API, the error message now includes the response body (JSON or text) so users can diagnose the root cause (e.g., rate limiting, access denied, invalid record). Previously, only the status code was shown, making it impossible to debug intermittent 403/429 errors without using a browser. Fixes #169 --- brainles_preprocessing/utils/zenodo.py | 10 +++++++++- tests/test_zenodo.py | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/brainles_preprocessing/utils/zenodo.py b/brainles_preprocessing/utils/zenodo.py index b43f9d7..dff9a10 100644 --- a/brainles_preprocessing/utils/zenodo.py +++ b/brainles_preprocessing/utils/zenodo.py @@ -137,9 +137,17 @@ def _get_metadata_and_archive_url(self) -> Tuple[Dict, str] | None: try: response = requests.get(f"{self.BASE_URL}/{self.record_id}") if response.status_code != 200: + # Include the response body for easier debugging. + # Zenodo returns JSON error details in the response body. + try: + error_detail = response.json() + except (ValueError, requests.exceptions.JSONDecodeError): + error_detail = response.text.strip() + error_msg = ( f"Cannot find record '{self.record_id}' on Zenodo " - f"({response.status_code=})." + f"(status_code={response.status_code}). " + f"Response: {error_detail}" ) logger.error(error_msg) raise ZenodoException(error_msg) diff --git a/tests/test_zenodo.py b/tests/test_zenodo.py index 7815b22..7653d4d 100644 --- a/tests/test_zenodo.py +++ b/tests/test_zenodo.py @@ -57,11 +57,28 @@ def test_get_metadata_and_archive_url_success( def test_get_metadata_and_archive_url_failure(monkeypatch): response_mock = MagicMock() response_mock.status_code = 404 + response_mock.json.return_value = { + "message": "The record does not exist.", + "status": 404, + } monkeypatch.setattr("requests.get", lambda *args, **kwargs: response_mock) record = ZenodoRecord("invalid", Path("/tmp"), "test") - with pytest.raises(ZenodoException): + with pytest.raises(ZenodoException, match="The record does not exist"): + record._get_metadata_and_archive_url() + + +def test_get_metadata_and_archive_url_failure_with_text_body(monkeypatch): + response_mock = MagicMock() + response_mock.status_code = 403 + response_mock.json.side_effect = ValueError("No JSON") + response_mock.text = "Rate limit exceeded. Please try again later." + + monkeypatch.setattr("requests.get", lambda *args, **kwargs: response_mock) + record = ZenodoRecord("123", Path("/tmp"), "test") + + with pytest.raises(ZenodoException, match="Rate limit exceeded"): record._get_metadata_and_archive_url() From 3193e053aa9a5f8678c872b6cd14599e30d08b4a Mon Sep 17 00:00:00 2001 From: rtmalikian Date: Thu, 18 Jun 2026 05:42:40 -0700 Subject: [PATCH 2/2] fix: address Copilot review comments 1. Catch ValueError only (requests.exceptions.JSONDecodeError is a subclass and may not exist in older requests versions) 2. Use status-code-aware error messages: 'Cannot find record' for 404, 'Failed to fetch record' for other status codes (403, 429, etc.) --- brainles_preprocessing/utils/zenodo.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/brainles_preprocessing/utils/zenodo.py b/brainles_preprocessing/utils/zenodo.py index dff9a10..e4b9f2b 100644 --- a/brainles_preprocessing/utils/zenodo.py +++ b/brainles_preprocessing/utils/zenodo.py @@ -141,11 +141,19 @@ def _get_metadata_and_archive_url(self) -> Tuple[Dict, str] | None: # Zenodo returns JSON error details in the response body. try: error_detail = response.json() - except (ValueError, requests.exceptions.JSONDecodeError): + except ValueError: error_detail = response.text.strip() + if response.status_code == 404: + error_prefix = ( + f"Cannot find record '{self.record_id}' on Zenodo" + ) + else: + error_prefix = ( + f"Failed to fetch record '{self.record_id}' from Zenodo" + ) error_msg = ( - f"Cannot find record '{self.record_id}' on Zenodo " + f"{error_prefix} " f"(status_code={response.status_code}). " f"Response: {error_detail}" )