from typing import Callable
from typing import Any
from typing import List
from typing import Dict
from typing import Tuple
import logging
import time


def try_n_times(
    fn: Callable[..., Any],
    args: List[Any],
    kwargs: Dict[Any, Any],
    errors: Tuple[Any],
    max_tries: int,
    sleep_time: float = 0,
) -> Any:
    """
    Keeps calling a function with given parameters until maximum number of tries, catching a set of given errors.
    :param fn: The function to call
    :param args: Argument list
    :param kwargs" Keyword arguments
    :param errors: Expected errors to be ignored
    :param max_tries: Maximal number of tries before raising error
    :param sleep_time: Time waited between subsequent tried to the function call.
    :return: The return value of the given function
    """
    assert max_tries > 0, "Number of tries must be a positive integer"
    attempts = 0
    error = None

    while attempts < max_tries:
        try:
            return fn(*args, **kwargs)
        except errors as e:
            error = e
            attempts += 1
            logging.warning(f"Tried to call {fn} raised {e}, trying {max_tries - attempts} more times.")
            time.sleep(sleep_time)
            pass

    if error:
        raise error