"""AWS retry utilities (ported from integration_scripts.utils.awsretry). Implements AWSRetry.backoff() decorator with exponential backoff and logging. Conceived based on a separate, novel implementation from 'Allen Sanabria'. """ from __future__ import annotations import time from functools import wraps from typing import Callable, Type from src.logger import get_logger logger = get_logger() class AWSRetry: """Retry helper with exponential backoff.""" @staticmethod def backoff( tries: int = 10, delay: int = 3, base_exception_class: Type[Exception] = Exception, ) -> Callable: """Decorator to retry a function with exponential backoff. - tries: Total attempts - delay: Initial delay (seconds), grows exponentially - base_exception_class: Exception type to catch """ def deco(f: Callable) -> Callable: @wraps(f) def retry_func(*args, **kwargs): max_tries, max_delay = tries, delay while max_tries > 1: try: logger.info( f'[AWSRetry] *** re-running "{f.__name__}" ' f'(kwargs: {kwargs} / args: {args} / tries left: ' f'{max_tries})' ) return f(*args, **kwargs) except base_exception_class as e: logger.error( '[AWSRetry] we got an exception: {}'.format( type(e).__name__ ) ) try: # Legacy behavior attempted to read response; # guard safely for non-ClientError exceptions. response_meta = getattr(e, 'response', {}) response_code = ( response_meta.get('ResponseMetadata', {}) .get('HTTPStatusCode') ) except Exception: response_code = None logger.error('[AWSRetry] exc #1: {}'.format(e)) logger.error( '[AWSRetry] exc response #2: {}'.format( getattr(e, 'response', None) ) ) logger.error( '[AWSRetry] exc response code #3: {}'.format( response_code ) ) time.sleep(max_delay) max_tries -= 1 max_delay *= 2 # final try return f(*args, **kwargs) return retry_func return deco