@@ -327,57 +327,23 @@ def _handle_client_error(
327327
328328 Raises APIError if error is not retryable or retries exhausted.
329329 """
330- tag = metadata ["tags" ][0 ]
331- operation = metadata ["operation" ]
332- reason = response .reason_phrase if hasattr (response , "reason_phrase" ) else ""
333- status = response .status_code
334-
335330 # Parse response body
336331 try :
337332 message = response .json ()
338333 except (ValueError , json .decoder .JSONDecodeError ):
339334 message = response .content [:100 ]
340335
341- # Network delete concurrency error
342- if self ._is_network_delete_concurrency (metadata , response , message ):
343- wait = random .randint (30 , self ._network_delete_retry_wait_time )
344- if self ._logger :
345- self ._logger .warning (
346- f"{ tag } , { operation } - { status } { reason } , retrying in { wait } seconds"
347- )
348- self ._sleep (wait )
349- retries -= 1
350- if retries == 0 :
351- raise APIError (metadata , response )
352- return retries
336+ # Determine wait time based on error type
337+ wait = self ._classify_client_error_wait (metadata , response , message )
353338
354- # Action batch concurrency error
355- if self ._is_action_batch_concurrency (message ):
356- wait = self ._action_batch_retry_wait_time
357- if self ._logger :
358- self ._logger .warning (
359- f"{ tag } , { operation } - { status } { reason } , retrying in { wait } seconds"
360- )
361- self ._sleep (wait )
362- retries -= 1
363- if retries == 0 :
364- raise APIError (metadata , response )
365- return retries
366-
367- # Generic 4xx retry
368- if self ._retry_4xx_error and retries > 0 :
369- wait = random .randint (1 , self ._retry_4xx_error_wait_time )
370- if self ._logger :
371- self ._logger .warning (
372- f"{ tag } , { operation } - { status } { reason } , retrying in { wait } seconds"
373- )
374- self ._sleep (wait )
375- retries -= 1
376- if retries == 0 :
377- raise APIError (metadata , response )
378- return retries
339+ if wait is not None :
340+ return self ._retry_with_wait (wait , metadata , response , retries )
379341
380342 # Non-retryable client error
343+ tag = metadata ["tags" ][0 ]
344+ operation = metadata ["operation" ]
345+ reason = response .reason_phrase if hasattr (response , "reason_phrase" ) else ""
346+ status = response .status_code
381347 if self ._logger :
382348 self ._logger .error (f"{ tag } , { operation } - { status } { reason } , { message } " )
383349 raise APIError (metadata , response )
@@ -386,6 +352,44 @@ def _handle_client_error(
386352 # Helper methods
387353 # ------------------------------------------------------------------
388354
355+ def _classify_client_error_wait (
356+ self ,
357+ metadata : Dict [str , Any ],
358+ response : "httpx.Response" ,
359+ message : Any ,
360+ ) -> Optional [float ]:
361+ """Determine retry wait time for a 4xx error, or None if non-retryable."""
362+ if self ._is_network_delete_concurrency (metadata , response , message ):
363+ return float (random .randint (30 , self ._network_delete_retry_wait_time ))
364+ if self ._is_action_batch_concurrency (message ):
365+ return float (self ._action_batch_retry_wait_time )
366+ if self ._retry_4xx_error :
367+ return float (random .randint (1 , self ._retry_4xx_error_wait_time ))
368+ return None
369+
370+ def _retry_with_wait (
371+ self ,
372+ wait : float ,
373+ metadata : Dict [str , Any ],
374+ response : "httpx.Response" ,
375+ retries : int ,
376+ ) -> int :
377+ """Log, sleep, decrement retries; raise APIError if exhausted."""
378+ tag = metadata ["tags" ][0 ]
379+ operation = metadata ["operation" ]
380+ reason = response .reason_phrase if hasattr (response , "reason_phrase" ) else ""
381+ status = response .status_code
382+
383+ if self ._logger :
384+ self ._logger .warning (
385+ f"{ tag } , { operation } - { status } { reason } , retrying in { wait } seconds"
386+ )
387+ self ._sleep (wait )
388+ retries -= 1
389+ if retries == 0 :
390+ raise APIError (metadata , response )
391+ return retries
392+
389393 def _is_network_delete_concurrency (
390394 self ,
391395 metadata : Dict [str , Any ],
0 commit comments