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


def keep_trying(
    fn: Callable[..., Any],
    args: List[Any],
    kwargs: Dict[Any, Any],
    errors: Tuple[Any],
    timeout: float,
    sleep_time: float = 0.1,
) -> Any:
    """
    Keeps calling a function with given parameters until timeout (at least once), 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 timeout: Maximal time before timeout (seconds)
    :param sleep_time: Time waited between subsequent tried to the function call.
    :return: The return value of the given function
    """
    assert timeout > 0, "Timeout must be a positive real number"
    start_time = time.time()
    max_time = start_time + timeout
    first_run = True

    while time.time() < max_time or first_run:
        try:
            return fn(*args, **kwargs), time.time() - start_time
        except errors:
            first_run = False
            time.sleep(sleep_time)

    raise TimeoutError(f"Timeout on function call {fn}({args}{kwargs}) catching {errors}")