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+
80118def _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