@@ -35,6 +35,10 @@ class StorageBatchOperationsTransientError(RuntimeError):
3535 """Raised when polling should retry after a transient API failure."""
3636
3737
38+ class StorageBatchOperationsAuthError (RuntimeError ):
39+ """Raised when polling should refresh credentials and retry once."""
40+
41+
3842def create_delete_job (
3943 * ,
4044 project_id : str ,
@@ -48,8 +52,8 @@ def create_delete_job(
4852 from google .auth .transport .requests import Request as GoogleAuthRequest
4953
5054 credentials , _ = default (scopes = ["https://www.googleapis.com/auth/cloud-platform" ])
51- if not credentials . valid :
52- credentials . refresh ( GoogleAuthRequest () )
55+ auth_request = GoogleAuthRequest ()
56+ _refresh_credentials_if_needed ( credentials , auth_request )
5357
5458 request_url = (
5559 "https://storagebatchoperations.googleapis.com/v1/"
@@ -116,28 +120,58 @@ def wait_for_delete_job(
116120 from google .auth .transport .requests import Request as GoogleAuthRequest
117121
118122 credentials , _ = default (scopes = ["https://www.googleapis.com/auth/cloud-platform" ])
119- if not credentials .valid :
120- credentials .refresh (GoogleAuthRequest ())
123+ auth_request = GoogleAuthRequest ()
121124
122125 request_url = f"https://storagebatchoperations.googleapis.com/v1/{ job_name } "
123126 elapsed = 0
127+ auth_retried = False
124128 while True :
125129 try :
126- payload = _get_job_payload ( request_url = request_url , token = credentials . token )
130+ _refresh_credentials_if_needed ( credentials , auth_request )
127131 except StorageBatchOperationsTransientError :
128- logger .debug (
129- "Polling Storage Batch Operations job %s hit transient error after %ss" ,
132+ elapsed = _sleep_for_transient (
133+ job_name = job_name ,
134+ timeout_seconds = timeout_seconds ,
135+ poll_interval_seconds = poll_interval_seconds ,
136+ elapsed = elapsed ,
137+ log_prefix = "Refreshing credentials while polling" ,
138+ )
139+ auth_retried = False
140+ continue
141+
142+ try :
143+ payload = _get_job_payload (request_url = request_url , token = credentials .token )
144+ except StorageBatchOperationsAuthError :
145+ if auth_retried :
146+ raise
147+ logger .info (
148+ "Refreshing credentials while polling Storage Batch Operations job %s" ,
130149 job_name ,
131- elapsed ,
132- exc_info = True ,
133150 )
134- elapsed = _sleep_or_raise_timeout (
151+ try :
152+ _refresh_credentials (credentials , auth_request )
153+ except StorageBatchOperationsTransientError :
154+ elapsed = _sleep_for_transient (
155+ job_name = job_name ,
156+ timeout_seconds = timeout_seconds ,
157+ poll_interval_seconds = poll_interval_seconds ,
158+ elapsed = elapsed ,
159+ log_prefix = "Refreshing credentials while polling" ,
160+ )
161+ continue
162+ auth_retried = True
163+ continue
164+ except StorageBatchOperationsTransientError :
165+ elapsed = _sleep_for_transient (
135166 job_name = job_name ,
136167 timeout_seconds = timeout_seconds ,
137168 poll_interval_seconds = poll_interval_seconds ,
138169 elapsed = elapsed ,
170+ log_prefix = "Polling" ,
139171 )
172+ auth_retried = False
140173 continue
174+ auth_retried = False
141175 state = str (payload .get ("state" ) or "STATE_UNSPECIFIED" )
142176 logger .debug (
143177 "Polling Storage Batch Operations job %s: state=%s elapsed=%ss" ,
@@ -168,6 +202,10 @@ def _get_job_payload(*, request_url: str, token: str) -> dict[str, object]:
168202 return json .loads (response .read ().decode ("utf-8" ))
169203 except HTTPError as exc :
170204 detail = exc .read ().decode ("utf-8" , errors = "replace" )
205+ if exc .code == 401 :
206+ raise StorageBatchOperationsAuthError (
207+ f"Storage Batch Operations get job authentication failure ({ exc .code } ): { detail } "
208+ ) from exc
171209 if exc .code in {429 , 500 , 502 , 503 , 504 }:
172210 raise StorageBatchOperationsTransientError (
173211 f"Storage Batch Operations get job transient failure ({ exc .code } ): { detail } "
@@ -179,6 +217,26 @@ def _get_job_payload(*, request_url: str, token: str) -> dict[str, object]:
179217 ) from exc
180218
181219
220+ def _refresh_credentials_if_needed (credentials , auth_request ) -> None :
221+ if not credentials .valid :
222+ _refresh_credentials (credentials , auth_request )
223+
224+
225+ def _refresh_credentials (credentials , auth_request ) -> None :
226+ from google .auth import exceptions as google_auth_exceptions
227+
228+ try :
229+ credentials .refresh (auth_request )
230+ except URLError as exc :
231+ raise StorageBatchOperationsTransientError (
232+ f"Storage Batch Operations credential refresh transient failure: { str (exc .reason )} "
233+ ) from exc
234+ except google_auth_exceptions .TransportError as exc :
235+ raise StorageBatchOperationsTransientError (
236+ f"Storage Batch Operations credential refresh transient failure: { exc } "
237+ ) from exc
238+
239+
182240def _coerce_job_status (
183241 * ,
184242 job_name : str ,
@@ -204,6 +262,29 @@ def _coerce_counter(value: object) -> int:
204262 return int (str (value ))
205263
206264
265+ def _sleep_for_transient (
266+ * ,
267+ job_name : str ,
268+ timeout_seconds : int ,
269+ poll_interval_seconds : int ,
270+ elapsed : int ,
271+ log_prefix : str ,
272+ ) -> int :
273+ logger .debug (
274+ "%s Storage Batch Operations job %s hit transient error after %ss" ,
275+ log_prefix ,
276+ job_name ,
277+ elapsed ,
278+ exc_info = True ,
279+ )
280+ return _sleep_or_raise_timeout (
281+ job_name = job_name ,
282+ timeout_seconds = timeout_seconds ,
283+ poll_interval_seconds = poll_interval_seconds ,
284+ elapsed = elapsed ,
285+ )
286+
287+
207288def _sleep_or_raise_timeout (
208289 * ,
209290 job_name : str ,
0 commit comments