retry.py 1.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. from __future__ import annotations
  2. import functools
  3. from time import perf_counter, sleep
  4. from typing import TYPE_CHECKING, Callable, TypeVar
  5. if TYPE_CHECKING:
  6. from typing_extensions import ParamSpec
  7. T = TypeVar("T")
  8. P = ParamSpec("P")
  9. def retry(
  10. wait: float, stop_after_delay: float
  11. ) -> Callable[[Callable[P, T]], Callable[P, T]]:
  12. """Decorator to automatically retry a function on error.
  13. If the function raises, the function is recalled with the same arguments
  14. until it returns or the time limit is reached. When the time limit is
  15. surpassed, the last exception raised is reraised.
  16. :param wait: The time to wait after an error before retrying, in seconds.
  17. :param stop_after_delay: The time limit after which retries will cease,
  18. in seconds.
  19. """
  20. def wrapper(func: Callable[P, T]) -> Callable[P, T]:
  21. @functools.wraps(func)
  22. def retry_wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
  23. # The performance counter is monotonic on all platforms we care
  24. # about and has much better resolution than time.monotonic().
  25. start_time = perf_counter()
  26. while True:
  27. try:
  28. return func(*args, **kwargs)
  29. except Exception:
  30. if perf_counter() - start_time > stop_after_delay:
  31. raise
  32. sleep(wait)
  33. return retry_wrapped
  34. return wrapper