Skip to content

Commit cb5b5c2

Browse files
committed
feat: Retry on HTTP 400 / "failedPrecondition"
Extracts out a function to decode the contents.
1 parent c62a278 commit cb5b5c2

File tree

1 file changed

+48
-38
lines changed

1 file changed

+48
-38
lines changed

googleapiclient/http.py

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,44 @@
7777
_LEGACY_BATCH_URI = "https://www.googleapis.com/batch"
7878

7979

80+
def _decode_reason_from_content(content) -> str | None:
81+
reason = None
82+
83+
if not content:
84+
return None
85+
86+
# Content is in JSON format.
87+
try:
88+
data = json.loads(content.decode("utf-8"))
89+
if isinstance(data, dict):
90+
# There are many variations of the error json so we need
91+
# to determine the keyword which has the error detail. Make sure
92+
# that the order of the keywords below isn't changed as it can
93+
# break user code. If the "errors" key exists, we must use that
94+
# first.
95+
# See Issue #1243
96+
# https://github.com/googleapis/google-api-python-client/issues/1243
97+
error_detail_keyword = next(
98+
(kw for kw in ["errors", "status", "message"] if kw in data["error"]),
99+
"",
100+
)
101+
102+
if error_detail_keyword:
103+
reason = data["error"][error_detail_keyword]
104+
105+
if isinstance(reason, list) and len(reason) > 0:
106+
reason = reason[0]
107+
if "reason" in reason:
108+
reason = reason["reason"]
109+
else:
110+
reason = data[0]["error"]["errors"]["reason"]
111+
except (UnicodeDecodeError, ValueError, KeyError):
112+
LOGGER.warning("Invalid JSON content from response: %s", content)
113+
return None
114+
115+
return reason
116+
117+
80118
def _should_retry_response(resp_status, content):
81119
"""Determines whether a response should be retried.
82120
@@ -87,8 +125,6 @@ def _should_retry_response(resp_status, content):
87125
Returns:
88126
True if the response should be retried, otherwise False.
89127
"""
90-
reason = None
91-
92128
# Retry on 5xx errors.
93129
if resp_status >= 500:
94130
return True
@@ -100,49 +136,23 @@ def _should_retry_response(resp_status, content):
100136
# For 403 errors, we have to check for the `reason` in the response to
101137
# determine if we should retry.
102138
if resp_status == http_client.FORBIDDEN:
103-
# If there's no details about the 403 type, don't retry.
104-
if not content:
105-
return False
106-
107-
# Content is in JSON format.
108-
try:
109-
data = json.loads(content.decode("utf-8"))
110-
if isinstance(data, dict):
111-
# There are many variations of the error json so we need
112-
# to determine the keyword which has the error detail. Make sure
113-
# that the order of the keywords below isn't changed as it can
114-
# break user code. If the "errors" key exists, we must use that
115-
# first.
116-
# See Issue #1243
117-
# https://github.com/googleapis/google-api-python-client/issues/1243
118-
error_detail_keyword = next(
119-
(
120-
kw
121-
for kw in ["errors", "status", "message"]
122-
if kw in data["error"]
123-
),
124-
"",
125-
)
126-
127-
if error_detail_keyword:
128-
reason = data["error"][error_detail_keyword]
129-
130-
if isinstance(reason, list) and len(reason) > 0:
131-
reason = reason[0]
132-
if "reason" in reason:
133-
reason = reason["reason"]
134-
else:
135-
reason = data[0]["error"]["errors"]["reason"]
136-
except (UnicodeDecodeError, ValueError, KeyError):
137-
LOGGER.warning("Invalid JSON content from response: %s", content)
138-
return False
139+
reason = _decode_reason_from_content(content)
139140

140141
LOGGER.warning('Encountered 403 Forbidden with reason "%s"', reason)
141142

142143
# Only retry on rate limit related failures.
143144
if reason in ("userRateLimitExceeded", "rateLimitExceeded"):
144145
return True
145146

147+
# We get 400 errors from gmail api with "Precondition check failed". These should be retried.
148+
if resp_status == http_client.BAD_REQUEST:
149+
reason = _decode_reason_from_content(content)
150+
151+
LOGGER.warning('Encountered 400 Bad Request with reason "%s"', reason)
152+
153+
if reason == "failedPrecondition":
154+
return True
155+
146156
# Everything else is a success or non-retriable so break.
147157
return False
148158

0 commit comments

Comments
 (0)