From 83a7067b22610cf456e125d4a647d9ed07c23d15 Mon Sep 17 00:00:00 2001 From: kwy404 Date: Sat, 26 Sep 2026 05:27:39 -0300 Subject: [PATCH] fix: avoid TypeError when a 403 error body is a JSON array _should_retry_response read data[0]["error"]["errors"]["reason"] for list-wrapped error bodies, but "errors" is a list, so any such 403 raised TypeError out of execute() instead of retrying on rate limits or raising HttpError. Index the first entry, as the dict branch does. Also make LIST_NOT_CONFIGURED_RESPONSE valid JSON so that test_no_retry_403_list_fails actually reaches the list branch. --- googleapiclient/http.py | 2 +- tests/test_http.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/googleapiclient/http.py b/googleapiclient/http.py index 5aad076854..ac13c3366d 100644 --- a/googleapiclient/http.py +++ b/googleapiclient/http.py @@ -132,7 +132,7 @@ def _should_retry_response(resp_status, content): if "reason" in reason: reason = reason["reason"] else: - reason = data[0]["error"]["errors"]["reason"] + reason = data[0]["error"]["errors"][0]["reason"] except (UnicodeDecodeError, ValueError, KeyError): LOGGER.warning("Invalid JSON content from response: %s", content) return False diff --git a/tests/test_http.py b/tests/test_http.py index 91f2c7ae52..9859a2e42b 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -895,7 +895,7 @@ def test_media_io_base_download_unknown_media_size(self): } }""" -LIST_NOT_CONFIGURED_RESPONSE = """[ +LIST_NOT_CONFIGURED_RESPONSE = """[{ "error": { "errors": [ { @@ -907,7 +907,7 @@ def test_media_io_base_download_unknown_media_size(self): "code": 403, "message": "Access Not Configured" } -]""" +}]""" class Callbacks(object): @@ -1170,6 +1170,19 @@ def test_no_retry_403_list_fails(self): request.execute() request._sleep.assert_not_called() + def test_retry_403_list_rate_limit(self): + content = json.dumps([json.loads(RATE_LIMIT_EXCEEDED_RESPONSE)]) + http = HttpMockSequence( + [({"status": "403"}, content), ({"status": "200"}, "{}")] + ) + model = JsonModel() + uri = "https://www.googleapis.com/someapi/v1/collection/?foo=bar" + request = HttpRequest(http, model.response, uri) + request._sleep = mock.MagicMock() + + self.assertEqual({}, request.execute(num_retries=1)) + request._sleep.assert_called_once() + def test_null_postproc(self): resp, content = HttpRequest.null_postproc("foo", "bar") self.assertEqual(resp, "foo")