aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/tenacity
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/tenacity
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/tenacity')
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/__init__.py720
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/_utils.py101
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/after.py51
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/asyncio/__init__.py206
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/asyncio/retry.py125
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/before.py48
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/before_sleep.py72
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/nap.py43
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/retry.py282
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/stop.py130
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/tornadoweb.py63
-rw-r--r--.venv/lib/python3.12/site-packages/tenacity/wait.py234
13 files changed, 2075 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/tenacity/__init__.py b/.venv/lib/python3.12/site-packages/tenacity/__init__.py
new file mode 100644
index 00000000..02057a07
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/__init__.py
@@ -0,0 +1,720 @@
+# Copyright 2016-2018 Julien Danjou
+# Copyright 2017 Elisey Zanko
+# Copyright 2016 Étienne Bersac
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import dataclasses
+import functools
+import sys
+import threading
+import time
+import typing as t
+import warnings
+from abc import ABC, abstractmethod
+from concurrent import futures
+
+from . import _utils
+
+# Import all built-in retry strategies for easier usage.
+from .retry import retry_base # noqa
+from .retry import retry_all # noqa
+from .retry import retry_always # noqa
+from .retry import retry_any # noqa
+from .retry import retry_if_exception # noqa
+from .retry import retry_if_exception_type # noqa
+from .retry import retry_if_exception_cause_type # noqa
+from .retry import retry_if_not_exception_type # noqa
+from .retry import retry_if_not_result # noqa
+from .retry import retry_if_result # noqa
+from .retry import retry_never # noqa
+from .retry import retry_unless_exception_type # noqa
+from .retry import retry_if_exception_message # noqa
+from .retry import retry_if_not_exception_message # noqa
+
+# Import all nap strategies for easier usage.
+from .nap import sleep # noqa
+from .nap import sleep_using_event # noqa
+
+# Import all built-in stop strategies for easier usage.
+from .stop import stop_after_attempt # noqa
+from .stop import stop_after_delay # noqa
+from .stop import stop_before_delay # noqa
+from .stop import stop_all # noqa
+from .stop import stop_any # noqa
+from .stop import stop_never # noqa
+from .stop import stop_when_event_set # noqa
+
+# Import all built-in wait strategies for easier usage.
+from .wait import wait_chain # noqa
+from .wait import wait_combine # noqa
+from .wait import wait_exponential # noqa
+from .wait import wait_fixed # noqa
+from .wait import wait_incrementing # noqa
+from .wait import wait_none # noqa
+from .wait import wait_random # noqa
+from .wait import wait_random_exponential # noqa
+from .wait import wait_random_exponential as wait_full_jitter # noqa
+from .wait import wait_exponential_jitter # noqa
+
+# Import all built-in before strategies for easier usage.
+from .before import before_log # noqa
+from .before import before_nothing # noqa
+
+# Import all built-in after strategies for easier usage.
+from .after import after_log # noqa
+from .after import after_nothing # noqa
+
+# Import all built-in after strategies for easier usage.
+from .before_sleep import before_sleep_log # noqa
+from .before_sleep import before_sleep_nothing # noqa
+
+try:
+ import tornado
+except ImportError:
+ tornado = None
+
+if t.TYPE_CHECKING:
+ import types
+
+ from . import asyncio as tasyncio
+ from .retry import RetryBaseT
+ from .stop import StopBaseT
+ from .wait import WaitBaseT
+
+
+WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
+WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any])
+
+
+dataclass_kwargs = {}
+if sys.version_info >= (3, 10):
+ dataclass_kwargs.update({"slots": True})
+
+
+@dataclasses.dataclass(**dataclass_kwargs)
+class IterState:
+ actions: t.List[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field(
+ default_factory=list
+ )
+ retry_run_result: bool = False
+ delay_since_first_attempt: int = 0
+ stop_run_result: bool = False
+ is_explicit_retry: bool = False
+
+ def reset(self) -> None:
+ self.actions = []
+ self.retry_run_result = False
+ self.delay_since_first_attempt = 0
+ self.stop_run_result = False
+ self.is_explicit_retry = False
+
+
+class TryAgain(Exception):
+ """Always retry the executed function when raised."""
+
+
+NO_RESULT = object()
+
+
+class DoAttempt:
+ pass
+
+
+class DoSleep(float):
+ pass
+
+
+class BaseAction:
+ """Base class for representing actions to take by retry object.
+
+ Concrete implementations must define:
+ - __init__: to initialize all necessary fields
+ - REPR_FIELDS: class variable specifying attributes to include in repr(self)
+ - NAME: for identification in retry object methods and callbacks
+ """
+
+ REPR_FIELDS: t.Sequence[str] = ()
+ NAME: t.Optional[str] = None
+
+ def __repr__(self) -> str:
+ state_str = ", ".join(
+ f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS
+ )
+ return f"{self.__class__.__name__}({state_str})"
+
+ def __str__(self) -> str:
+ return repr(self)
+
+
+class RetryAction(BaseAction):
+ REPR_FIELDS = ("sleep",)
+ NAME = "retry"
+
+ def __init__(self, sleep: t.SupportsFloat) -> None:
+ self.sleep = float(sleep)
+
+
+_unset = object()
+
+
+def _first_set(first: t.Union[t.Any, object], second: t.Any) -> t.Any:
+ return second if first is _unset else first
+
+
+class RetryError(Exception):
+ """Encapsulates the last attempt instance right before giving up."""
+
+ def __init__(self, last_attempt: "Future") -> None:
+ self.last_attempt = last_attempt
+ super().__init__(last_attempt)
+
+ def reraise(self) -> t.NoReturn:
+ if self.last_attempt.failed:
+ raise self.last_attempt.result()
+ raise self
+
+ def __str__(self) -> str:
+ return f"{self.__class__.__name__}[{self.last_attempt}]"
+
+
+class AttemptManager:
+ """Manage attempt context."""
+
+ def __init__(self, retry_state: "RetryCallState"):
+ self.retry_state = retry_state
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(
+ self,
+ exc_type: t.Optional[t.Type[BaseException]],
+ exc_value: t.Optional[BaseException],
+ traceback: t.Optional["types.TracebackType"],
+ ) -> t.Optional[bool]:
+ if exc_type is not None and exc_value is not None:
+ self.retry_state.set_exception((exc_type, exc_value, traceback))
+ return True # Swallow exception.
+ else:
+ # We don't have the result, actually.
+ self.retry_state.set_result(None)
+ return None
+
+
+class BaseRetrying(ABC):
+ def __init__(
+ self,
+ sleep: t.Callable[[t.Union[int, float]], None] = sleep,
+ stop: "StopBaseT" = stop_never,
+ wait: "WaitBaseT" = wait_none(),
+ retry: "RetryBaseT" = retry_if_exception_type(),
+ before: t.Callable[["RetryCallState"], None] = before_nothing,
+ after: t.Callable[["RetryCallState"], None] = after_nothing,
+ before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None,
+ reraise: bool = False,
+ retry_error_cls: t.Type[RetryError] = RetryError,
+ retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None,
+ ):
+ self.sleep = sleep
+ self.stop = stop
+ self.wait = wait
+ self.retry = retry
+ self.before = before
+ self.after = after
+ self.before_sleep = before_sleep
+ self.reraise = reraise
+ self._local = threading.local()
+ self.retry_error_cls = retry_error_cls
+ self.retry_error_callback = retry_error_callback
+
+ def copy(
+ self,
+ sleep: t.Union[t.Callable[[t.Union[int, float]], None], object] = _unset,
+ stop: t.Union["StopBaseT", object] = _unset,
+ wait: t.Union["WaitBaseT", object] = _unset,
+ retry: t.Union[retry_base, object] = _unset,
+ before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
+ after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
+ before_sleep: t.Union[
+ t.Optional[t.Callable[["RetryCallState"], None]], object
+ ] = _unset,
+ reraise: t.Union[bool, object] = _unset,
+ retry_error_cls: t.Union[t.Type[RetryError], object] = _unset,
+ retry_error_callback: t.Union[
+ t.Optional[t.Callable[["RetryCallState"], t.Any]], object
+ ] = _unset,
+ ) -> "BaseRetrying":
+ """Copy this object with some parameters changed if needed."""
+ return self.__class__(
+ sleep=_first_set(sleep, self.sleep),
+ stop=_first_set(stop, self.stop),
+ wait=_first_set(wait, self.wait),
+ retry=_first_set(retry, self.retry),
+ before=_first_set(before, self.before),
+ after=_first_set(after, self.after),
+ before_sleep=_first_set(before_sleep, self.before_sleep),
+ reraise=_first_set(reraise, self.reraise),
+ retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls),
+ retry_error_callback=_first_set(
+ retry_error_callback, self.retry_error_callback
+ ),
+ )
+
+ def __repr__(self) -> str:
+ return (
+ f"<{self.__class__.__name__} object at 0x{id(self):x} ("
+ f"stop={self.stop}, "
+ f"wait={self.wait}, "
+ f"sleep={self.sleep}, "
+ f"retry={self.retry}, "
+ f"before={self.before}, "
+ f"after={self.after})>"
+ )
+
+ @property
+ def statistics(self) -> t.Dict[str, t.Any]:
+ """Return a dictionary of runtime statistics.
+
+ This dictionary will be empty when the controller has never been
+ ran. When it is running or has ran previously it should have (but
+ may not) have useful and/or informational keys and values when
+ running is underway and/or completed.
+
+ .. warning:: The keys in this dictionary **should** be some what
+ stable (not changing), but there existence **may**
+ change between major releases as new statistics are
+ gathered or removed so before accessing keys ensure that
+ they actually exist and handle when they do not.
+
+ .. note:: The values in this dictionary are local to the thread
+ running call (so if multiple threads share the same retrying
+ object - either directly or indirectly) they will each have
+ there own view of statistics they have collected (in the
+ future we may provide a way to aggregate the various
+ statistics from each thread).
+ """
+ try:
+ return self._local.statistics # type: ignore[no-any-return]
+ except AttributeError:
+ self._local.statistics = t.cast(t.Dict[str, t.Any], {})
+ return self._local.statistics
+
+ @property
+ def iter_state(self) -> IterState:
+ try:
+ return self._local.iter_state # type: ignore[no-any-return]
+ except AttributeError:
+ self._local.iter_state = IterState()
+ return self._local.iter_state
+
+ def wraps(self, f: WrappedFn) -> WrappedFn:
+ """Wrap a function for retrying.
+
+ :param f: A function to wraps for retrying.
+ """
+
+ @functools.wraps(
+ f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
+ )
+ def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
+ # Always create a copy to prevent overwriting the local contexts when
+ # calling the same wrapped functions multiple times in the same stack
+ copy = self.copy()
+ wrapped_f.statistics = copy.statistics # type: ignore[attr-defined]
+ return copy(f, *args, **kw)
+
+ def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn:
+ return self.copy(*args, **kwargs).wraps(f)
+
+ # Preserve attributes
+ wrapped_f.retry = self # type: ignore[attr-defined]
+ wrapped_f.retry_with = retry_with # type: ignore[attr-defined]
+ wrapped_f.statistics = {} # type: ignore[attr-defined]
+
+ return wrapped_f # type: ignore[return-value]
+
+ def begin(self) -> None:
+ self.statistics.clear()
+ self.statistics["start_time"] = time.monotonic()
+ self.statistics["attempt_number"] = 1
+ self.statistics["idle_for"] = 0
+
+ def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
+ self.iter_state.actions.append(fn)
+
+ def _run_retry(self, retry_state: "RetryCallState") -> None:
+ self.iter_state.retry_run_result = self.retry(retry_state)
+
+ def _run_wait(self, retry_state: "RetryCallState") -> None:
+ if self.wait:
+ sleep = self.wait(retry_state)
+ else:
+ sleep = 0.0
+
+ retry_state.upcoming_sleep = sleep
+
+ def _run_stop(self, retry_state: "RetryCallState") -> None:
+ self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
+ self.iter_state.stop_run_result = self.stop(retry_state)
+
+ def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa
+ self._begin_iter(retry_state)
+ result = None
+ for action in self.iter_state.actions:
+ result = action(retry_state)
+ return result
+
+ def _begin_iter(self, retry_state: "RetryCallState") -> None: # noqa
+ self.iter_state.reset()
+
+ fut = retry_state.outcome
+ if fut is None:
+ if self.before is not None:
+ self._add_action_func(self.before)
+ self._add_action_func(lambda rs: DoAttempt())
+ return
+
+ self.iter_state.is_explicit_retry = fut.failed and isinstance(
+ fut.exception(), TryAgain
+ )
+ if not self.iter_state.is_explicit_retry:
+ self._add_action_func(self._run_retry)
+ self._add_action_func(self._post_retry_check_actions)
+
+ def _post_retry_check_actions(self, retry_state: "RetryCallState") -> None:
+ if not (self.iter_state.is_explicit_retry or self.iter_state.retry_run_result):
+ self._add_action_func(lambda rs: rs.outcome.result())
+ return
+
+ if self.after is not None:
+ self._add_action_func(self.after)
+
+ self._add_action_func(self._run_wait)
+ self._add_action_func(self._run_stop)
+ self._add_action_func(self._post_stop_check_actions)
+
+ def _post_stop_check_actions(self, retry_state: "RetryCallState") -> None:
+ if self.iter_state.stop_run_result:
+ if self.retry_error_callback:
+ self._add_action_func(self.retry_error_callback)
+ return
+
+ def exc_check(rs: "RetryCallState") -> None:
+ fut = t.cast(Future, rs.outcome)
+ retry_exc = self.retry_error_cls(fut)
+ if self.reraise:
+ raise retry_exc.reraise()
+ raise retry_exc from fut.exception()
+
+ self._add_action_func(exc_check)
+ return
+
+ def next_action(rs: "RetryCallState") -> None:
+ sleep = rs.upcoming_sleep
+ rs.next_action = RetryAction(sleep)
+ rs.idle_for += sleep
+ self.statistics["idle_for"] += sleep
+ self.statistics["attempt_number"] += 1
+
+ self._add_action_func(next_action)
+
+ if self.before_sleep is not None:
+ self._add_action_func(self.before_sleep)
+
+ self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep))
+
+ def __iter__(self) -> t.Generator[AttemptManager, None, None]:
+ self.begin()
+
+ retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
+ while True:
+ do = self.iter(retry_state=retry_state)
+ if isinstance(do, DoAttempt):
+ yield AttemptManager(retry_state=retry_state)
+ elif isinstance(do, DoSleep):
+ retry_state.prepare_for_next_attempt()
+ self.sleep(do)
+ else:
+ break
+
+ @abstractmethod
+ def __call__(
+ self,
+ fn: t.Callable[..., WrappedFnReturnT],
+ *args: t.Any,
+ **kwargs: t.Any,
+ ) -> WrappedFnReturnT:
+ pass
+
+
+class Retrying(BaseRetrying):
+ """Retrying controller."""
+
+ def __call__(
+ self,
+ fn: t.Callable[..., WrappedFnReturnT],
+ *args: t.Any,
+ **kwargs: t.Any,
+ ) -> WrappedFnReturnT:
+ self.begin()
+
+ retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
+ while True:
+ do = self.iter(retry_state=retry_state)
+ if isinstance(do, DoAttempt):
+ try:
+ result = fn(*args, **kwargs)
+ except BaseException: # noqa: B902
+ retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type]
+ else:
+ retry_state.set_result(result)
+ elif isinstance(do, DoSleep):
+ retry_state.prepare_for_next_attempt()
+ self.sleep(do)
+ else:
+ return do # type: ignore[no-any-return]
+
+
+if sys.version_info >= (3, 9):
+ FutureGenericT = futures.Future[t.Any]
+else:
+ FutureGenericT = futures.Future
+
+
+class Future(FutureGenericT):
+ """Encapsulates a (future or past) attempted call to a target function."""
+
+ def __init__(self, attempt_number: int) -> None:
+ super().__init__()
+ self.attempt_number = attempt_number
+
+ @property
+ def failed(self) -> bool:
+ """Return whether a exception is being held in this future."""
+ return self.exception() is not None
+
+ @classmethod
+ def construct(
+ cls, attempt_number: int, value: t.Any, has_exception: bool
+ ) -> "Future":
+ """Construct a new Future object."""
+ fut = cls(attempt_number)
+ if has_exception:
+ fut.set_exception(value)
+ else:
+ fut.set_result(value)
+ return fut
+
+
+class RetryCallState:
+ """State related to a single call wrapped with Retrying."""
+
+ def __init__(
+ self,
+ retry_object: BaseRetrying,
+ fn: t.Optional[WrappedFn],
+ args: t.Any,
+ kwargs: t.Any,
+ ) -> None:
+ #: Retry call start timestamp
+ self.start_time = time.monotonic()
+ #: Retry manager object
+ self.retry_object = retry_object
+ #: Function wrapped by this retry call
+ self.fn = fn
+ #: Arguments of the function wrapped by this retry call
+ self.args = args
+ #: Keyword arguments of the function wrapped by this retry call
+ self.kwargs = kwargs
+
+ #: The number of the current attempt
+ self.attempt_number: int = 1
+ #: Last outcome (result or exception) produced by the function
+ self.outcome: t.Optional[Future] = None
+ #: Timestamp of the last outcome
+ self.outcome_timestamp: t.Optional[float] = None
+ #: Time spent sleeping in retries
+ self.idle_for: float = 0.0
+ #: Next action as decided by the retry manager
+ self.next_action: t.Optional[RetryAction] = None
+ #: Next sleep time as decided by the retry manager.
+ self.upcoming_sleep: float = 0.0
+
+ @property
+ def seconds_since_start(self) -> t.Optional[float]:
+ if self.outcome_timestamp is None:
+ return None
+ return self.outcome_timestamp - self.start_time
+
+ def prepare_for_next_attempt(self) -> None:
+ self.outcome = None
+ self.outcome_timestamp = None
+ self.attempt_number += 1
+ self.next_action = None
+
+ def set_result(self, val: t.Any) -> None:
+ ts = time.monotonic()
+ fut = Future(self.attempt_number)
+ fut.set_result(val)
+ self.outcome, self.outcome_timestamp = fut, ts
+
+ def set_exception(
+ self,
+ exc_info: t.Tuple[
+ t.Type[BaseException], BaseException, "types.TracebackType| None"
+ ],
+ ) -> None:
+ ts = time.monotonic()
+ fut = Future(self.attempt_number)
+ fut.set_exception(exc_info[1])
+ self.outcome, self.outcome_timestamp = fut, ts
+
+ def __repr__(self) -> str:
+ if self.outcome is None:
+ result = "none yet"
+ elif self.outcome.failed:
+ exception = self.outcome.exception()
+ result = f"failed ({exception.__class__.__name__} {exception})"
+ else:
+ result = f"returned {self.outcome.result()}"
+
+ slept = float(round(self.idle_for, 2))
+ clsname = self.__class__.__name__
+ return f"<{clsname} {id(self)}: attempt #{self.attempt_number}; slept for {slept}; last result: {result}>"
+
+
+@t.overload
+def retry(func: WrappedFn) -> WrappedFn: ...
+
+
+@t.overload
+def retry(
+ sleep: t.Callable[[t.Union[int, float]], t.Union[None, t.Awaitable[None]]] = sleep,
+ stop: "StopBaseT" = stop_never,
+ wait: "WaitBaseT" = wait_none(),
+ retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = retry_if_exception_type(),
+ before: t.Callable[
+ ["RetryCallState"], t.Union[None, t.Awaitable[None]]
+ ] = before_nothing,
+ after: t.Callable[
+ ["RetryCallState"], t.Union[None, t.Awaitable[None]]
+ ] = after_nothing,
+ before_sleep: t.Optional[
+ t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
+ ] = None,
+ reraise: bool = False,
+ retry_error_cls: t.Type["RetryError"] = RetryError,
+ retry_error_callback: t.Optional[
+ t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
+ ] = None,
+) -> t.Callable[[WrappedFn], WrappedFn]: ...
+
+
+def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
+ """Wrap a function with a new `Retrying` object.
+
+ :param dargs: positional arguments passed to Retrying object
+ :param dkw: keyword arguments passed to the Retrying object
+ """
+ # support both @retry and @retry() as valid syntax
+ if len(dargs) == 1 and callable(dargs[0]):
+ return retry()(dargs[0])
+ else:
+
+ def wrap(f: WrappedFn) -> WrappedFn:
+ if isinstance(f, retry_base):
+ warnings.warn(
+ f"Got retry_base instance ({f.__class__.__name__}) as callable argument, "
+ f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)"
+ )
+ r: "BaseRetrying"
+ if _utils.is_coroutine_callable(f):
+ r = AsyncRetrying(*dargs, **dkw)
+ elif (
+ tornado
+ and hasattr(tornado.gen, "is_coroutine_function")
+ and tornado.gen.is_coroutine_function(f)
+ ):
+ r = TornadoRetrying(*dargs, **dkw)
+ else:
+ r = Retrying(*dargs, **dkw)
+
+ return r.wraps(f)
+
+ return wrap
+
+
+from tenacity.asyncio import AsyncRetrying # noqa:E402,I100
+
+if tornado:
+ from tenacity.tornadoweb import TornadoRetrying
+
+
+__all__ = [
+ "retry_base",
+ "retry_all",
+ "retry_always",
+ "retry_any",
+ "retry_if_exception",
+ "retry_if_exception_type",
+ "retry_if_exception_cause_type",
+ "retry_if_not_exception_type",
+ "retry_if_not_result",
+ "retry_if_result",
+ "retry_never",
+ "retry_unless_exception_type",
+ "retry_if_exception_message",
+ "retry_if_not_exception_message",
+ "sleep",
+ "sleep_using_event",
+ "stop_after_attempt",
+ "stop_after_delay",
+ "stop_before_delay",
+ "stop_all",
+ "stop_any",
+ "stop_never",
+ "stop_when_event_set",
+ "wait_chain",
+ "wait_combine",
+ "wait_exponential",
+ "wait_fixed",
+ "wait_incrementing",
+ "wait_none",
+ "wait_random",
+ "wait_random_exponential",
+ "wait_full_jitter",
+ "wait_exponential_jitter",
+ "before_log",
+ "before_nothing",
+ "after_log",
+ "after_nothing",
+ "before_sleep_log",
+ "before_sleep_nothing",
+ "retry",
+ "WrappedFn",
+ "TryAgain",
+ "NO_RESULT",
+ "DoAttempt",
+ "DoSleep",
+ "BaseAction",
+ "RetryAction",
+ "RetryError",
+ "AttemptManager",
+ "BaseRetrying",
+ "Retrying",
+ "Future",
+ "RetryCallState",
+ "AsyncRetrying",
+]
diff --git a/.venv/lib/python3.12/site-packages/tenacity/_utils.py b/.venv/lib/python3.12/site-packages/tenacity/_utils.py
new file mode 100644
index 00000000..f11a0888
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/_utils.py
@@ -0,0 +1,101 @@
+# Copyright 2016 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import functools
+import inspect
+import sys
+import typing
+from datetime import timedelta
+
+
+# sys.maxsize:
+# An integer giving the maximum value a variable of type Py_ssize_t can take.
+MAX_WAIT = sys.maxsize / 2
+
+
+def find_ordinal(pos_num: int) -> str:
+ # See: https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers
+ if pos_num == 0:
+ return "th"
+ elif pos_num == 1:
+ return "st"
+ elif pos_num == 2:
+ return "nd"
+ elif pos_num == 3:
+ return "rd"
+ elif 4 <= pos_num <= 20:
+ return "th"
+ else:
+ return find_ordinal(pos_num % 10)
+
+
+def to_ordinal(pos_num: int) -> str:
+ return f"{pos_num}{find_ordinal(pos_num)}"
+
+
+def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str:
+ """Get a callback fully-qualified name.
+
+ If no name can be produced ``repr(cb)`` is called and returned.
+ """
+ segments = []
+ try:
+ segments.append(cb.__qualname__)
+ except AttributeError:
+ try:
+ segments.append(cb.__name__)
+ except AttributeError:
+ pass
+ if not segments:
+ return repr(cb)
+ else:
+ try:
+ # When running under sphinx it appears this can be none?
+ if cb.__module__:
+ segments.insert(0, cb.__module__)
+ except AttributeError:
+ pass
+ return ".".join(segments)
+
+
+time_unit_type = typing.Union[int, float, timedelta]
+
+
+def to_seconds(time_unit: time_unit_type) -> float:
+ return float(
+ time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit
+ )
+
+
+def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:
+ if inspect.isclass(call):
+ return False
+ if inspect.iscoroutinefunction(call):
+ return True
+ partial_call = isinstance(call, functools.partial) and call.func
+ dunder_call = partial_call or getattr(call, "__call__", None)
+ return inspect.iscoroutinefunction(dunder_call)
+
+
+def wrap_to_async_func(
+ call: typing.Callable[..., typing.Any],
+) -> typing.Callable[..., typing.Awaitable[typing.Any]]:
+ if is_coroutine_callable(call):
+ return call
+
+ async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
+ return call(*args, **kwargs)
+
+ return inner
diff --git a/.venv/lib/python3.12/site-packages/tenacity/after.py b/.venv/lib/python3.12/site-packages/tenacity/after.py
new file mode 100644
index 00000000..aa3cc9df
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/after.py
@@ -0,0 +1,51 @@
+# Copyright 2016 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import typing
+
+from tenacity import _utils
+
+if typing.TYPE_CHECKING:
+ import logging
+
+ from tenacity import RetryCallState
+
+
+def after_nothing(retry_state: "RetryCallState") -> None:
+ """After call strategy that does nothing."""
+
+
+def after_log(
+ logger: "logging.Logger",
+ log_level: int,
+ sec_format: str = "%0.3f",
+) -> typing.Callable[["RetryCallState"], None]:
+ """After call strategy that logs to some logger the finished attempt."""
+
+ def log_it(retry_state: "RetryCallState") -> None:
+ if retry_state.fn is None:
+ # NOTE(sileht): can't really happen, but we must please mypy
+ fn_name = "<unknown>"
+ else:
+ fn_name = _utils.get_callback_name(retry_state.fn)
+ logger.log(
+ log_level,
+ f"Finished call to '{fn_name}' "
+ f"after {sec_format % retry_state.seconds_since_start}(s), "
+ f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
+ )
+
+ return log_it
diff --git a/.venv/lib/python3.12/site-packages/tenacity/asyncio/__init__.py b/.venv/lib/python3.12/site-packages/tenacity/asyncio/__init__.py
new file mode 100644
index 00000000..a9260914
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/asyncio/__init__.py
@@ -0,0 +1,206 @@
+# Copyright 2016 Étienne Bersac
+# Copyright 2016 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import functools
+import sys
+import typing as t
+
+import tenacity
+from tenacity import AttemptManager
+from tenacity import BaseRetrying
+from tenacity import DoAttempt
+from tenacity import DoSleep
+from tenacity import RetryCallState
+from tenacity import RetryError
+from tenacity import after_nothing
+from tenacity import before_nothing
+from tenacity import _utils
+
+# Import all built-in retry strategies for easier usage.
+from .retry import RetryBaseT
+from .retry import retry_all # noqa
+from .retry import retry_any # noqa
+from .retry import retry_if_exception # noqa
+from .retry import retry_if_result # noqa
+from ..retry import RetryBaseT as SyncRetryBaseT
+
+if t.TYPE_CHECKING:
+ from tenacity.stop import StopBaseT
+ from tenacity.wait import WaitBaseT
+
+WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
+WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
+
+
+def _portable_async_sleep(seconds: float) -> t.Awaitable[None]:
+ # If trio is already imported, then importing it is cheap.
+ # If trio isn't already imported, then it's definitely not running, so we
+ # can skip further checks.
+ if "trio" in sys.modules:
+ # If trio is available, then sniffio is too
+ import trio
+ import sniffio
+
+ if sniffio.current_async_library() == "trio":
+ return trio.sleep(seconds)
+ # Otherwise, assume asyncio
+ # Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).
+ import asyncio
+
+ return asyncio.sleep(seconds)
+
+
+class AsyncRetrying(BaseRetrying):
+ def __init__(
+ self,
+ sleep: t.Callable[
+ [t.Union[int, float]], t.Union[None, t.Awaitable[None]]
+ ] = _portable_async_sleep,
+ stop: "StopBaseT" = tenacity.stop.stop_never,
+ wait: "WaitBaseT" = tenacity.wait.wait_none(),
+ retry: "t.Union[SyncRetryBaseT, RetryBaseT]" = tenacity.retry_if_exception_type(),
+ before: t.Callable[
+ ["RetryCallState"], t.Union[None, t.Awaitable[None]]
+ ] = before_nothing,
+ after: t.Callable[
+ ["RetryCallState"], t.Union[None, t.Awaitable[None]]
+ ] = after_nothing,
+ before_sleep: t.Optional[
+ t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
+ ] = None,
+ reraise: bool = False,
+ retry_error_cls: t.Type["RetryError"] = RetryError,
+ retry_error_callback: t.Optional[
+ t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
+ ] = None,
+ ) -> None:
+ super().__init__(
+ sleep=sleep, # type: ignore[arg-type]
+ stop=stop,
+ wait=wait,
+ retry=retry, # type: ignore[arg-type]
+ before=before, # type: ignore[arg-type]
+ after=after, # type: ignore[arg-type]
+ before_sleep=before_sleep, # type: ignore[arg-type]
+ reraise=reraise,
+ retry_error_cls=retry_error_cls,
+ retry_error_callback=retry_error_callback,
+ )
+
+ async def __call__( # type: ignore[override]
+ self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any
+ ) -> WrappedFnReturnT:
+ self.begin()
+
+ retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
+ while True:
+ do = await self.iter(retry_state=retry_state)
+ if isinstance(do, DoAttempt):
+ try:
+ result = await fn(*args, **kwargs)
+ except BaseException: # noqa: B902
+ retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type]
+ else:
+ retry_state.set_result(result)
+ elif isinstance(do, DoSleep):
+ retry_state.prepare_for_next_attempt()
+ await self.sleep(do) # type: ignore[misc]
+ else:
+ return do # type: ignore[no-any-return]
+
+ def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
+ self.iter_state.actions.append(_utils.wrap_to_async_func(fn))
+
+ async def _run_retry(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
+ self.iter_state.retry_run_result = await _utils.wrap_to_async_func(self.retry)(
+ retry_state
+ )
+
+ async def _run_wait(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
+ if self.wait:
+ sleep = await _utils.wrap_to_async_func(self.wait)(retry_state)
+ else:
+ sleep = 0.0
+
+ retry_state.upcoming_sleep = sleep
+
+ async def _run_stop(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
+ self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
+ self.iter_state.stop_run_result = await _utils.wrap_to_async_func(self.stop)(
+ retry_state
+ )
+
+ async def iter(
+ self, retry_state: "RetryCallState"
+ ) -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa: A003
+ self._begin_iter(retry_state)
+ result = None
+ for action in self.iter_state.actions:
+ result = await action(retry_state)
+ return result
+
+ def __iter__(self) -> t.Generator[AttemptManager, None, None]:
+ raise TypeError("AsyncRetrying object is not iterable")
+
+ def __aiter__(self) -> "AsyncRetrying":
+ self.begin()
+ self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
+ return self
+
+ async def __anext__(self) -> AttemptManager:
+ while True:
+ do = await self.iter(retry_state=self._retry_state)
+ if do is None:
+ raise StopAsyncIteration
+ elif isinstance(do, DoAttempt):
+ return AttemptManager(retry_state=self._retry_state)
+ elif isinstance(do, DoSleep):
+ self._retry_state.prepare_for_next_attempt()
+ await self.sleep(do) # type: ignore[misc]
+ else:
+ raise StopAsyncIteration
+
+ def wraps(self, fn: WrappedFn) -> WrappedFn:
+ wrapped = super().wraps(fn)
+ # Ensure wrapper is recognized as a coroutine function.
+
+ @functools.wraps(
+ fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
+ )
+ async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
+ # Always create a copy to prevent overwriting the local contexts when
+ # calling the same wrapped functions multiple times in the same stack
+ copy = self.copy()
+ async_wrapped.statistics = copy.statistics # type: ignore[attr-defined]
+ return await copy(fn, *args, **kwargs)
+
+ # Preserve attributes
+ async_wrapped.retry = self # type: ignore[attr-defined]
+ async_wrapped.retry_with = wrapped.retry_with # type: ignore[attr-defined]
+ async_wrapped.statistics = {} # type: ignore[attr-defined]
+
+ return async_wrapped # type: ignore[return-value]
+
+
+__all__ = [
+ "retry_all",
+ "retry_any",
+ "retry_if_exception",
+ "retry_if_result",
+ "WrappedFn",
+ "AsyncRetrying",
+]
diff --git a/.venv/lib/python3.12/site-packages/tenacity/asyncio/retry.py b/.venv/lib/python3.12/site-packages/tenacity/asyncio/retry.py
new file mode 100644
index 00000000..94b8b154
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/asyncio/retry.py
@@ -0,0 +1,125 @@
+# Copyright 2016–2021 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import abc
+import typing
+
+from tenacity import _utils
+from tenacity import retry_base
+
+if typing.TYPE_CHECKING:
+ from tenacity import RetryCallState
+
+
+class async_retry_base(retry_base):
+ """Abstract base class for async retry strategies."""
+
+ @abc.abstractmethod
+ async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
+ pass
+
+ def __and__( # type: ignore[override]
+ self, other: "typing.Union[retry_base, async_retry_base]"
+ ) -> "retry_all":
+ return retry_all(self, other)
+
+ def __rand__( # type: ignore[misc,override]
+ self, other: "typing.Union[retry_base, async_retry_base]"
+ ) -> "retry_all":
+ return retry_all(other, self)
+
+ def __or__( # type: ignore[override]
+ self, other: "typing.Union[retry_base, async_retry_base]"
+ ) -> "retry_any":
+ return retry_any(self, other)
+
+ def __ror__( # type: ignore[misc,override]
+ self, other: "typing.Union[retry_base, async_retry_base]"
+ ) -> "retry_any":
+ return retry_any(other, self)
+
+
+RetryBaseT = typing.Union[
+ async_retry_base, typing.Callable[["RetryCallState"], typing.Awaitable[bool]]
+]
+
+
+class retry_if_exception(async_retry_base):
+ """Retry strategy that retries if an exception verifies a predicate."""
+
+ def __init__(
+ self, predicate: typing.Callable[[BaseException], typing.Awaitable[bool]]
+ ) -> None:
+ self.predicate = predicate
+
+ async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
+ if retry_state.outcome is None:
+ raise RuntimeError("__call__() called before outcome was set")
+
+ if retry_state.outcome.failed:
+ exception = retry_state.outcome.exception()
+ if exception is None:
+ raise RuntimeError("outcome failed but the exception is None")
+ return await self.predicate(exception)
+ else:
+ return False
+
+
+class retry_if_result(async_retry_base):
+ """Retries if the result verifies a predicate."""
+
+ def __init__(
+ self, predicate: typing.Callable[[typing.Any], typing.Awaitable[bool]]
+ ) -> None:
+ self.predicate = predicate
+
+ async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
+ if retry_state.outcome is None:
+ raise RuntimeError("__call__() called before outcome was set")
+
+ if not retry_state.outcome.failed:
+ return await self.predicate(retry_state.outcome.result())
+ else:
+ return False
+
+
+class retry_any(async_retry_base):
+ """Retries if any of the retries condition is valid."""
+
+ def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None:
+ self.retries = retries
+
+ async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
+ result = False
+ for r in self.retries:
+ result = result or await _utils.wrap_to_async_func(r)(retry_state)
+ if result:
+ break
+ return result
+
+
+class retry_all(async_retry_base):
+ """Retries if all the retries condition are valid."""
+
+ def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None:
+ self.retries = retries
+
+ async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
+ result = True
+ for r in self.retries:
+ result = result and await _utils.wrap_to_async_func(r)(retry_state)
+ if not result:
+ break
+ return result
diff --git a/.venv/lib/python3.12/site-packages/tenacity/before.py b/.venv/lib/python3.12/site-packages/tenacity/before.py
new file mode 100644
index 00000000..366235af
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/before.py
@@ -0,0 +1,48 @@
+# Copyright 2016 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import typing
+
+from tenacity import _utils
+
+if typing.TYPE_CHECKING:
+ import logging
+
+ from tenacity import RetryCallState
+
+
+def before_nothing(retry_state: "RetryCallState") -> None:
+ """Before call strategy that does nothing."""
+
+
+def before_log(
+ logger: "logging.Logger", log_level: int
+) -> typing.Callable[["RetryCallState"], None]:
+ """Before call strategy that logs to some logger the attempt."""
+
+ def log_it(retry_state: "RetryCallState") -> None:
+ if retry_state.fn is None:
+ # NOTE(sileht): can't really happen, but we must please mypy
+ fn_name = "<unknown>"
+ else:
+ fn_name = _utils.get_callback_name(retry_state.fn)
+ logger.log(
+ log_level,
+ f"Starting call to '{fn_name}', "
+ f"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
+ )
+
+ return log_it
diff --git a/.venv/lib/python3.12/site-packages/tenacity/before_sleep.py b/.venv/lib/python3.12/site-packages/tenacity/before_sleep.py
new file mode 100644
index 00000000..d04edcf9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/before_sleep.py
@@ -0,0 +1,72 @@
+# Copyright 2016 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import typing
+
+from tenacity import _utils
+
+if typing.TYPE_CHECKING:
+ import logging
+
+ from tenacity import RetryCallState
+
+
+def before_sleep_nothing(retry_state: "RetryCallState") -> None:
+ """Before call strategy that does nothing."""
+
+
+def before_sleep_log(
+ logger: "logging.Logger",
+ log_level: int,
+ exc_info: bool = False,
+) -> typing.Callable[["RetryCallState"], None]:
+ """Before call strategy that logs to some logger the attempt."""
+
+ def log_it(retry_state: "RetryCallState") -> None:
+ local_exc_info: BaseException | bool | None
+
+ if retry_state.outcome is None:
+ raise RuntimeError("log_it() called before outcome was set")
+
+ if retry_state.next_action is None:
+ raise RuntimeError("log_it() called before next_action was set")
+
+ if retry_state.outcome.failed:
+ ex = retry_state.outcome.exception()
+ verb, value = "raised", f"{ex.__class__.__name__}: {ex}"
+
+ if exc_info:
+ local_exc_info = retry_state.outcome.exception()
+ else:
+ local_exc_info = False
+ else:
+ verb, value = "returned", retry_state.outcome.result()
+ local_exc_info = False # exc_info does not apply when no exception
+
+ if retry_state.fn is None:
+ # NOTE(sileht): can't really happen, but we must please mypy
+ fn_name = "<unknown>"
+ else:
+ fn_name = _utils.get_callback_name(retry_state.fn)
+
+ logger.log(
+ log_level,
+ f"Retrying {fn_name} "
+ f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
+ exc_info=local_exc_info,
+ )
+
+ return log_it
diff --git a/.venv/lib/python3.12/site-packages/tenacity/nap.py b/.venv/lib/python3.12/site-packages/tenacity/nap.py
new file mode 100644
index 00000000..72aa5bfd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/nap.py
@@ -0,0 +1,43 @@
+# Copyright 2016 Étienne Bersac
+# Copyright 2016 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import time
+import typing
+
+if typing.TYPE_CHECKING:
+ import threading
+
+
+def sleep(seconds: float) -> None:
+ """
+ Sleep strategy that delays execution for a given number of seconds.
+
+ This is the default strategy, and may be mocked out for unit testing.
+ """
+ time.sleep(seconds)
+
+
+class sleep_using_event:
+ """Sleep strategy that waits on an event to be set."""
+
+ def __init__(self, event: "threading.Event") -> None:
+ self.event = event
+
+ def __call__(self, timeout: typing.Optional[float]) -> None:
+ # NOTE(harlowja): this may *not* actually wait for timeout
+ # seconds if the event is set (ie this may eject out early).
+ self.event.wait(timeout=timeout)
diff --git a/.venv/lib/python3.12/site-packages/tenacity/py.typed b/.venv/lib/python3.12/site-packages/tenacity/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/py.typed
diff --git a/.venv/lib/python3.12/site-packages/tenacity/retry.py b/.venv/lib/python3.12/site-packages/tenacity/retry.py
new file mode 100644
index 00000000..9211631b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/retry.py
@@ -0,0 +1,282 @@
+# Copyright 2016–2021 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import abc
+import re
+import typing
+
+if typing.TYPE_CHECKING:
+ from tenacity import RetryCallState
+
+
+class retry_base(abc.ABC):
+ """Abstract base class for retry strategies."""
+
+ @abc.abstractmethod
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ pass
+
+ def __and__(self, other: "retry_base") -> "retry_all":
+ return other.__rand__(self)
+
+ def __rand__(self, other: "retry_base") -> "retry_all":
+ return retry_all(other, self)
+
+ def __or__(self, other: "retry_base") -> "retry_any":
+ return other.__ror__(self)
+
+ def __ror__(self, other: "retry_base") -> "retry_any":
+ return retry_any(other, self)
+
+
+RetryBaseT = typing.Union[retry_base, typing.Callable[["RetryCallState"], bool]]
+
+
+class _retry_never(retry_base):
+ """Retry strategy that never rejects any result."""
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return False
+
+
+retry_never = _retry_never()
+
+
+class _retry_always(retry_base):
+ """Retry strategy that always rejects any result."""
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return True
+
+
+retry_always = _retry_always()
+
+
+class retry_if_exception(retry_base):
+ """Retry strategy that retries if an exception verifies a predicate."""
+
+ def __init__(self, predicate: typing.Callable[[BaseException], bool]) -> None:
+ self.predicate = predicate
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.outcome is None:
+ raise RuntimeError("__call__() called before outcome was set")
+
+ if retry_state.outcome.failed:
+ exception = retry_state.outcome.exception()
+ if exception is None:
+ raise RuntimeError("outcome failed but the exception is None")
+ return self.predicate(exception)
+ else:
+ return False
+
+
+class retry_if_exception_type(retry_if_exception):
+ """Retries if an exception has been raised of one or more types."""
+
+ def __init__(
+ self,
+ exception_types: typing.Union[
+ typing.Type[BaseException],
+ typing.Tuple[typing.Type[BaseException], ...],
+ ] = Exception,
+ ) -> None:
+ self.exception_types = exception_types
+ super().__init__(lambda e: isinstance(e, exception_types))
+
+
+class retry_if_not_exception_type(retry_if_exception):
+ """Retries except an exception has been raised of one or more types."""
+
+ def __init__(
+ self,
+ exception_types: typing.Union[
+ typing.Type[BaseException],
+ typing.Tuple[typing.Type[BaseException], ...],
+ ] = Exception,
+ ) -> None:
+ self.exception_types = exception_types
+ super().__init__(lambda e: not isinstance(e, exception_types))
+
+
+class retry_unless_exception_type(retry_if_exception):
+ """Retries until an exception is raised of one or more types."""
+
+ def __init__(
+ self,
+ exception_types: typing.Union[
+ typing.Type[BaseException],
+ typing.Tuple[typing.Type[BaseException], ...],
+ ] = Exception,
+ ) -> None:
+ self.exception_types = exception_types
+ super().__init__(lambda e: not isinstance(e, exception_types))
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.outcome is None:
+ raise RuntimeError("__call__() called before outcome was set")
+
+ # always retry if no exception was raised
+ if not retry_state.outcome.failed:
+ return True
+
+ exception = retry_state.outcome.exception()
+ if exception is None:
+ raise RuntimeError("outcome failed but the exception is None")
+ return self.predicate(exception)
+
+
+class retry_if_exception_cause_type(retry_base):
+ """Retries if any of the causes of the raised exception is of one or more types.
+
+ The check on the type of the cause of the exception is done recursively (until finding
+ an exception in the chain that has no `__cause__`)
+ """
+
+ def __init__(
+ self,
+ exception_types: typing.Union[
+ typing.Type[BaseException],
+ typing.Tuple[typing.Type[BaseException], ...],
+ ] = Exception,
+ ) -> None:
+ self.exception_cause_types = exception_types
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.outcome is None:
+ raise RuntimeError("__call__ called before outcome was set")
+
+ if retry_state.outcome.failed:
+ exc = retry_state.outcome.exception()
+ while exc is not None:
+ if isinstance(exc.__cause__, self.exception_cause_types):
+ return True
+ exc = exc.__cause__
+
+ return False
+
+
+class retry_if_result(retry_base):
+ """Retries if the result verifies a predicate."""
+
+ def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None:
+ self.predicate = predicate
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.outcome is None:
+ raise RuntimeError("__call__() called before outcome was set")
+
+ if not retry_state.outcome.failed:
+ return self.predicate(retry_state.outcome.result())
+ else:
+ return False
+
+
+class retry_if_not_result(retry_base):
+ """Retries if the result refutes a predicate."""
+
+ def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None:
+ self.predicate = predicate
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.outcome is None:
+ raise RuntimeError("__call__() called before outcome was set")
+
+ if not retry_state.outcome.failed:
+ return not self.predicate(retry_state.outcome.result())
+ else:
+ return False
+
+
+class retry_if_exception_message(retry_if_exception):
+ """Retries if an exception message equals or matches."""
+
+ def __init__(
+ self,
+ message: typing.Optional[str] = None,
+ match: typing.Optional[str] = None,
+ ) -> None:
+ if message and match:
+ raise TypeError(
+ f"{self.__class__.__name__}() takes either 'message' or 'match', not both"
+ )
+
+ # set predicate
+ if message:
+
+ def message_fnc(exception: BaseException) -> bool:
+ return message == str(exception)
+
+ predicate = message_fnc
+ elif match:
+ prog = re.compile(match)
+
+ def match_fnc(exception: BaseException) -> bool:
+ return bool(prog.match(str(exception)))
+
+ predicate = match_fnc
+ else:
+ raise TypeError(
+ f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'"
+ )
+
+ super().__init__(predicate)
+
+
+class retry_if_not_exception_message(retry_if_exception_message):
+ """Retries until an exception message equals or matches."""
+
+ def __init__(
+ self,
+ message: typing.Optional[str] = None,
+ match: typing.Optional[str] = None,
+ ) -> None:
+ super().__init__(message, match)
+ # invert predicate
+ if_predicate = self.predicate
+ self.predicate = lambda *args_, **kwargs_: not if_predicate(*args_, **kwargs_)
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.outcome is None:
+ raise RuntimeError("__call__() called before outcome was set")
+
+ if not retry_state.outcome.failed:
+ return True
+
+ exception = retry_state.outcome.exception()
+ if exception is None:
+ raise RuntimeError("outcome failed but the exception is None")
+ return self.predicate(exception)
+
+
+class retry_any(retry_base):
+ """Retries if any of the retries condition is valid."""
+
+ def __init__(self, *retries: retry_base) -> None:
+ self.retries = retries
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return any(r(retry_state) for r in self.retries)
+
+
+class retry_all(retry_base):
+ """Retries if all the retries condition are valid."""
+
+ def __init__(self, *retries: retry_base) -> None:
+ self.retries = retries
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return all(r(retry_state) for r in self.retries)
diff --git a/.venv/lib/python3.12/site-packages/tenacity/stop.py b/.venv/lib/python3.12/site-packages/tenacity/stop.py
new file mode 100644
index 00000000..5cda59ab
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/stop.py
@@ -0,0 +1,130 @@
+# Copyright 2016–2021 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import abc
+import typing
+
+from tenacity import _utils
+
+if typing.TYPE_CHECKING:
+ import threading
+
+ from tenacity import RetryCallState
+
+
+class stop_base(abc.ABC):
+ """Abstract base class for stop strategies."""
+
+ @abc.abstractmethod
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ pass
+
+ def __and__(self, other: "stop_base") -> "stop_all":
+ return stop_all(self, other)
+
+ def __or__(self, other: "stop_base") -> "stop_any":
+ return stop_any(self, other)
+
+
+StopBaseT = typing.Union[stop_base, typing.Callable[["RetryCallState"], bool]]
+
+
+class stop_any(stop_base):
+ """Stop if any of the stop condition is valid."""
+
+ def __init__(self, *stops: stop_base) -> None:
+ self.stops = stops
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return any(x(retry_state) for x in self.stops)
+
+
+class stop_all(stop_base):
+ """Stop if all the stop conditions are valid."""
+
+ def __init__(self, *stops: stop_base) -> None:
+ self.stops = stops
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return all(x(retry_state) for x in self.stops)
+
+
+class _stop_never(stop_base):
+ """Never stop."""
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return False
+
+
+stop_never = _stop_never()
+
+
+class stop_when_event_set(stop_base):
+ """Stop when the given event is set."""
+
+ def __init__(self, event: "threading.Event") -> None:
+ self.event = event
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return self.event.is_set()
+
+
+class stop_after_attempt(stop_base):
+ """Stop when the previous attempt >= max_attempt."""
+
+ def __init__(self, max_attempt_number: int) -> None:
+ self.max_attempt_number = max_attempt_number
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ return retry_state.attempt_number >= self.max_attempt_number
+
+
+class stop_after_delay(stop_base):
+ """
+ Stop when the time from the first attempt >= limit.
+
+ Note: `max_delay` will be exceeded, so when used with a `wait`, the actual total delay will be greater
+ than `max_delay` by some of the final sleep period before `max_delay` is exceeded.
+
+ If you need stricter timing with waits, consider `stop_before_delay` instead.
+ """
+
+ def __init__(self, max_delay: _utils.time_unit_type) -> None:
+ self.max_delay = _utils.to_seconds(max_delay)
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.seconds_since_start is None:
+ raise RuntimeError("__call__() called but seconds_since_start is not set")
+ return retry_state.seconds_since_start >= self.max_delay
+
+
+class stop_before_delay(stop_base):
+ """
+ Stop right before the next attempt would take place after the time from the first attempt >= limit.
+
+ Most useful when you are using with a `wait` function like wait_random_exponential, but need to make
+ sure that the max_delay is not exceeded.
+ """
+
+ def __init__(self, max_delay: _utils.time_unit_type) -> None:
+ self.max_delay = _utils.to_seconds(max_delay)
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.seconds_since_start is None:
+ raise RuntimeError("__call__() called but seconds_since_start is not set")
+ return (
+ retry_state.seconds_since_start + retry_state.upcoming_sleep
+ >= self.max_delay
+ )
diff --git a/.venv/lib/python3.12/site-packages/tenacity/tornadoweb.py b/.venv/lib/python3.12/site-packages/tenacity/tornadoweb.py
new file mode 100644
index 00000000..44323e40
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/tornadoweb.py
@@ -0,0 +1,63 @@
+# Copyright 2017 Elisey Zanko
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import typing
+
+from tenacity import BaseRetrying
+from tenacity import DoAttempt
+from tenacity import DoSleep
+from tenacity import RetryCallState
+
+from tornado import gen
+
+if typing.TYPE_CHECKING:
+ from tornado.concurrent import Future
+
+_RetValT = typing.TypeVar("_RetValT")
+
+
+class TornadoRetrying(BaseRetrying):
+ def __init__(
+ self,
+ sleep: "typing.Callable[[float], Future[None]]" = gen.sleep,
+ **kwargs: typing.Any,
+ ) -> None:
+ super().__init__(**kwargs)
+ self.sleep = sleep
+
+ @gen.coroutine # type: ignore[misc]
+ def __call__(
+ self,
+ fn: "typing.Callable[..., typing.Union[typing.Generator[typing.Any, typing.Any, _RetValT], Future[_RetValT]]]",
+ *args: typing.Any,
+ **kwargs: typing.Any,
+ ) -> "typing.Generator[typing.Any, typing.Any, _RetValT]":
+ self.begin()
+
+ retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
+ while True:
+ do = self.iter(retry_state=retry_state)
+ if isinstance(do, DoAttempt):
+ try:
+ result = yield fn(*args, **kwargs)
+ except BaseException: # noqa: B902
+ retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type]
+ else:
+ retry_state.set_result(result)
+ elif isinstance(do, DoSleep):
+ retry_state.prepare_for_next_attempt()
+ yield self.sleep(do)
+ else:
+ raise gen.Return(do)
diff --git a/.venv/lib/python3.12/site-packages/tenacity/wait.py b/.venv/lib/python3.12/site-packages/tenacity/wait.py
new file mode 100644
index 00000000..dc3c8505
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/tenacity/wait.py
@@ -0,0 +1,234 @@
+# Copyright 2016–2021 Julien Danjou
+# Copyright 2016 Joshua Harlow
+# Copyright 2013-2014 Ray Holder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import abc
+import random
+import typing
+
+from tenacity import _utils
+
+if typing.TYPE_CHECKING:
+ from tenacity import RetryCallState
+
+
+class wait_base(abc.ABC):
+ """Abstract base class for wait strategies."""
+
+ @abc.abstractmethod
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ pass
+
+ def __add__(self, other: "wait_base") -> "wait_combine":
+ return wait_combine(self, other)
+
+ def __radd__(self, other: "wait_base") -> typing.Union["wait_combine", "wait_base"]:
+ # make it possible to use multiple waits with the built-in sum function
+ if other == 0: # type: ignore[comparison-overlap]
+ return self
+ return self.__add__(other)
+
+
+WaitBaseT = typing.Union[
+ wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]
+]
+
+
+class wait_fixed(wait_base):
+ """Wait strategy that waits a fixed amount of time between each retry."""
+
+ def __init__(self, wait: _utils.time_unit_type) -> None:
+ self.wait_fixed = _utils.to_seconds(wait)
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ return self.wait_fixed
+
+
+class wait_none(wait_fixed):
+ """Wait strategy that doesn't wait at all before retrying."""
+
+ def __init__(self) -> None:
+ super().__init__(0)
+
+
+class wait_random(wait_base):
+ """Wait strategy that waits a random amount of time between min/max."""
+
+ def __init__(
+ self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1
+ ) -> None: # noqa
+ self.wait_random_min = _utils.to_seconds(min)
+ self.wait_random_max = _utils.to_seconds(max)
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ return self.wait_random_min + (
+ random.random() * (self.wait_random_max - self.wait_random_min)
+ )
+
+
+class wait_combine(wait_base):
+ """Combine several waiting strategies."""
+
+ def __init__(self, *strategies: wait_base) -> None:
+ self.wait_funcs = strategies
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ return sum(x(retry_state=retry_state) for x in self.wait_funcs)
+
+
+class wait_chain(wait_base):
+ """Chain two or more waiting strategies.
+
+ If all strategies are exhausted, the very last strategy is used
+ thereafter.
+
+ For example::
+
+ @retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] +
+ [wait_fixed(2) for j in range(5)] +
+ [wait_fixed(5) for k in range(4)))
+ def wait_chained():
+ print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s
+ thereafter.")
+ """
+
+ def __init__(self, *strategies: wait_base) -> None:
+ self.strategies = strategies
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ wait_func_no = min(max(retry_state.attempt_number, 1), len(self.strategies))
+ wait_func = self.strategies[wait_func_no - 1]
+ return wait_func(retry_state=retry_state)
+
+
+class wait_incrementing(wait_base):
+ """Wait an incremental amount of time after each attempt.
+
+ Starting at a starting value and incrementing by a value for each attempt
+ (and restricting the upper limit to some maximum value).
+ """
+
+ def __init__(
+ self,
+ start: _utils.time_unit_type = 0,
+ increment: _utils.time_unit_type = 100,
+ max: _utils.time_unit_type = _utils.MAX_WAIT, # noqa
+ ) -> None:
+ self.start = _utils.to_seconds(start)
+ self.increment = _utils.to_seconds(increment)
+ self.max = _utils.to_seconds(max)
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ result = self.start + (self.increment * (retry_state.attempt_number - 1))
+ return max(0, min(result, self.max))
+
+
+class wait_exponential(wait_base):
+ """Wait strategy that applies exponential backoff.
+
+ It allows for a customized multiplier and an ability to restrict the
+ upper and lower limits to some maximum and minimum value.
+
+ The intervals are fixed (i.e. there is no jitter), so this strategy is
+ suitable for balancing retries against latency when a required resource is
+ unavailable for an unknown duration, but *not* suitable for resolving
+ contention between multiple processes for a shared resource. Use
+ wait_random_exponential for the latter case.
+ """
+
+ def __init__(
+ self,
+ multiplier: typing.Union[int, float] = 1,
+ max: _utils.time_unit_type = _utils.MAX_WAIT, # noqa
+ exp_base: typing.Union[int, float] = 2,
+ min: _utils.time_unit_type = 0, # noqa
+ ) -> None:
+ self.multiplier = multiplier
+ self.min = _utils.to_seconds(min)
+ self.max = _utils.to_seconds(max)
+ self.exp_base = exp_base
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ try:
+ exp = self.exp_base ** (retry_state.attempt_number - 1)
+ result = self.multiplier * exp
+ except OverflowError:
+ return self.max
+ return max(max(0, self.min), min(result, self.max))
+
+
+class wait_random_exponential(wait_exponential):
+ """Random wait with exponentially widening window.
+
+ An exponential backoff strategy used to mediate contention between multiple
+ uncoordinated processes for a shared resource in distributed systems. This
+ is the sense in which "exponential backoff" is meant in e.g. Ethernet
+ networking, and corresponds to the "Full Jitter" algorithm described in
+ this blog post:
+
+ https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
+
+ Each retry occurs at a random time in a geometrically expanding interval.
+ It allows for a custom multiplier and an ability to restrict the upper
+ limit of the random interval to some maximum value.
+
+ Example::
+
+ wait_random_exponential(multiplier=0.5, # initial window 0.5s
+ max=60) # max 60s timeout
+
+ When waiting for an unavailable resource to become available again, as
+ opposed to trying to resolve contention for a shared resource, the
+ wait_exponential strategy (which uses a fixed interval) may be preferable.
+
+ """
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ high = super().__call__(retry_state=retry_state)
+ return random.uniform(self.min, high)
+
+
+class wait_exponential_jitter(wait_base):
+ """Wait strategy that applies exponential backoff and jitter.
+
+ It allows for a customized initial wait, maximum wait and jitter.
+
+ This implements the strategy described here:
+ https://cloud.google.com/storage/docs/retry-strategy
+
+ The wait time is min(initial * 2**n + random.uniform(0, jitter), maximum)
+ where n is the retry count.
+ """
+
+ def __init__(
+ self,
+ initial: float = 1,
+ max: float = _utils.MAX_WAIT, # noqa
+ exp_base: float = 2,
+ jitter: float = 1,
+ ) -> None:
+ self.initial = initial
+ self.max = max
+ self.exp_base = exp_base
+ self.jitter = jitter
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ jitter = random.uniform(0, self.jitter)
+ try:
+ exp = self.exp_base ** (retry_state.attempt_number - 1)
+ result = self.initial * exp + jitter
+ except OverflowError:
+ result = self.max
+ return max(0, min(result, self.max))