from __future__ import print_function from functools import wraps import time class RetryCountExceededError(Exception): """Raised by @retry decorator when it exceeds the defined retry count """ def __str__(self): return 'Method retry count exceeded' def retry(error_condition=lambda ex: True, retry_count=10, retry_timeout=0.5, progressive_timeout=True): """Decorator for retrying function call in case of exception You could decorate any function or method with this if you need to repeatedly call this method a couple of times with an increasing interval in case of some error raised during the method call. Args: error_condition (callable(error)): Function that will check whether we should do retries for a particular error. E.g. you can check error class, some it's fields or values. retry_count (int): Number ot retries retry_timeout (int): Timeout in seconds to wait between retries progressive_timeout (bool): If True, the timeout value will be increased by 0.5 sec during each consecutive retry Raises: RetryCountExceededError: Error is being raised in case of retry count exceeded """ def wrapper(fn): @wraps(fn) def wrapped(*args, **kwargs): retries = 0 timeout = retry_timeout while retries < retry_count: try: result = fn(*args, **kwargs) return result except Exception as err: if error_condition(err): print('Error in retryable function: {}'.format( str(err))) print('Sleeping for {} and trying again.'.format( timeout)) time.sleep(timeout) retries += 1 timeout += 0.5 if progressive_timeout else 0 else: raise err raise RetryCountExceededError return wrapped return wrapper