Source code for prereise.gather.request_util
import functools
import time
from urllib.error import HTTPError
[docs]class TransientError(Exception):
    """Used for errors which can be retried"""
    pass 
[docs]class RateLimit:
    """Provides a way to call an arbitrary function at most once per interval.
    :param int/float interval: the amount of time in seconds to wait between actions
    """
    def __init__(self, interval=None):
        """Constructor"""
        self.interval = interval
        self.last_run_at = None if interval is None else time.time() - interval
[docs]    def invoke(self, action):
        """Call the action and return its value, waiting if necessary
        :param callable action: the thing to do
        :return: (*Any*) -- the return value of the action
        """
        if self.interval is None:
            return action()
        elapsed = time.time() - self.last_run_at
        if elapsed < self.interval:
            time.sleep(self.interval - elapsed)
        result = action()
        self.last_run_at = time.time()
        return result  
[docs]def rate_limit(_func=None, interval=None):
    def decorator(func):
        limiter = RateLimit(interval)
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return limiter.invoke(lambda: func(*args, **kwargs))
        return wrapper
    return decorator if _func is None else decorator(_func) 
[docs]def retry(
    _func=None,
    max_attempts=5,
    interval=None,
    raises=False,
    allowed_exceptions=(HTTPError),
):
    """Creates a decorator to handle retry logic.
    :param int max_attempts: the max number of retries
    :param int/float interval: minimum spacing between retries
    :param bool raises: whether to re-raise the error after max_attempts is reached
    :param tuple allowed_exceptions: exceptions for which the function will be retried, all others will be surfaced to the caller
    :return: (*Any*) -- the return value of the decorated function, or None if
        raises is False and all attempts failed
    """
    def decorator(func):
        limiter = RateLimit(interval)
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            wrapper.retry_count = 0
            for i in range(max_attempts):
                wrapper.retry_count = i + 1
                try:
                    return limiter.invoke(lambda: func(*args, **kwargs))
                except allowed_exceptions as e:
                    if wrapper.retry_count == max_attempts:
                        print("Max retries reached!!")
                        if raises:
                            raise e
        return wrapper
    return decorator if _func is None else decorator(_func)