def retry_func(func, args=None, kwargs=None, max_tries=3, delay=5, backoff=2, exceptions=None):
    """Retry a function call using an exponential backoff.

    Args:
        func: Function to call.
        args: Arguments to pass to the function.
        kwargs: Keyword arguments to pass to the function.
        max_tries: Maximum number of retries.
        delay: Initial delay between retries in seconds.
        backoff: Backoff multiplier e.g. value of 2 will double the delay each retry.
        exceptions: Exceptions to catch. If None, catch all exceptions. Can be a single exception or a tuple
            of exceptions.

    """
    args = args or []
    kwargs = kwargs or {}
    exceptions = exceptions or Exception
    num_tries, sleep_time = 0, delay
    while num_tries < max_tries:
        try:
            logger.debug("Calling function '%s'. Try %d of %d...", func.__name__, num_tries + 1, max_tries)
            out = func(*args, **kwargs)
            logger.debug("Succeeded.")
            return out
        except exceptions as e:
            num_tries += 1
            if num_tries == max_tries:
                logger.exception("The operation failed after the maximum number of retries.")
                raise
            logger.debug("Failed with %s. Retrying in %d seconds...", e, sleep_time)
            time.sleep(sleep_time)
            sleep_time *= backoff
    return None
Retry a function call using an exponential backoff.

    Args:
        func: Function to call.
        args: Arguments to pass to the function.
        kwargs: Keyword arguments to pass to the function.
        max_tries: Maximum number of retries.
        delay: Initial delay between retries in seconds.
        backoff: Backoff multiplier e.g. value of 2 will double the delay each retry.
        exceptions: Exceptions to catch. If None, catch all exceptions. Can be a single exception or a tuple
            of exceptions.

    
olive/common/utils.py