Change venv

This commit is contained in:
Ambulance Clerc
2023-05-31 08:31:22 +02:00
parent fb6f579089
commit fdbb52c96f
466 changed files with 25899 additions and 64721 deletions

View File

@@ -16,6 +16,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import sys
import threading
@@ -33,6 +34,7 @@ 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
@@ -63,6 +65,7 @@ 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
@@ -86,51 +89,13 @@ tornado = None # type: ignore
if t.TYPE_CHECKING:
import types
from .wait import wait_base
from .stop import stop_base
from .retry import RetryBaseT
from .stop import StopBaseT
from .wait import WaitBaseT
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable)
_RetValT = t.TypeVar("_RetValT")
@t.overload
def retry(fn: WrappedFn) -> WrappedFn:
pass
@t.overload
def retry(*dargs: t.Any, **dkw: t.Any) -> t.Callable[[WrappedFn], WrappedFn]: # noqa
pass
def retry(*dargs: t.Any, **dkw: t.Any) -> t.Union[WrappedFn, t.Callable[[WrappedFn], WrappedFn]]: # noqa
"""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__}(...)?)"
)
if iscoroutinefunction(f):
r: "BaseRetrying" = 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
WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any])
class TryAgain(Exception):
@@ -214,7 +179,7 @@ class AttemptManager:
exc_value: t.Optional[BaseException],
traceback: t.Optional["types.TracebackType"],
) -> t.Optional[bool]:
if isinstance(exc_value, BaseException):
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:
@@ -227,9 +192,9 @@ class BaseRetrying(ABC):
def __init__(
self,
sleep: t.Callable[[t.Union[int, float]], None] = sleep,
stop: "stop_base" = stop_never,
wait: "wait_base" = wait_none(),
retry: retry_base = retry_if_exception_type(),
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,
@@ -252,8 +217,8 @@ class BaseRetrying(ABC):
def copy(
self,
sleep: t.Union[t.Callable[[t.Union[int, float]], None], object] = _unset,
stop: t.Union["stop_base", object] = _unset,
wait: t.Union["wait_base", 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,
@@ -310,9 +275,9 @@ class BaseRetrying(ABC):
statistics from each thread).
"""
try:
return self._local.statistics
return self._local.statistics # type: ignore[no-any-return]
except AttributeError:
self._local.statistics = {}
self._local.statistics = t.cast(t.Dict[str, t.Any], {})
return self._local.statistics
def wraps(self, f: WrappedFn) -> WrappedFn:
@@ -328,10 +293,10 @@ class BaseRetrying(ABC):
def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn:
return self.copy(*args, **kwargs).wraps(f)
wrapped_f.retry = self
wrapped_f.retry_with = retry_with
wrapped_f.retry = self # type: ignore[attr-defined]
wrapped_f.retry_with = retry_with # type: ignore[attr-defined]
return wrapped_f
return wrapped_f # type: ignore[return-value]
def begin(self) -> None:
self.statistics.clear()
@@ -346,15 +311,15 @@ class BaseRetrying(ABC):
self.before(retry_state)
return DoAttempt()
is_explicit_retry = retry_state.outcome.failed and isinstance(retry_state.outcome.exception(), TryAgain)
if not (is_explicit_retry or self.retry(retry_state=retry_state)):
is_explicit_retry = fut.failed and isinstance(fut.exception(), TryAgain)
if not (is_explicit_retry or self.retry(retry_state)):
return fut.result()
if self.after is not None:
self.after(retry_state)
self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
if self.stop(retry_state=retry_state):
if self.stop(retry_state):
if self.retry_error_callback:
return self.retry_error_callback(retry_state)
retry_exc = self.retry_error_cls(fut)
@@ -363,7 +328,7 @@ class BaseRetrying(ABC):
raise retry_exc from fut.exception()
if self.wait:
sleep = self.wait(retry_state=retry_state)
sleep = self.wait(retry_state)
else:
sleep = 0.0
retry_state.next_action = RetryAction(sleep)
@@ -391,14 +356,24 @@ class BaseRetrying(ABC):
break
@abstractmethod
def __call__(self, fn: t.Callable[..., _RetValT], *args: t.Any, **kwargs: t.Any) -> _RetValT:
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[..., _RetValT], *args: t.Any, **kwargs: t.Any) -> _RetValT:
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)
@@ -408,17 +383,23 @@ class Retrying(BaseRetrying):
try:
result = fn(*args, **kwargs)
except BaseException: # noqa: B902
retry_state.set_exception(sys.exc_info())
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
return do # type: ignore[no-any-return]
class Future(futures.Future):
if sys.version_info[1] >= 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:
@@ -491,13 +472,15 @@ class RetryCallState:
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:
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):
def __repr__(self) -> str:
if self.outcome is None:
result = "none yet"
elif self.outcome.failed:
@@ -511,7 +494,115 @@ class RetryCallState:
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]], 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,
) -> 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 iscoroutinefunction(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 pip._vendor.tenacity._asyncio import AsyncRetrying # noqa:E402,I100
if tornado:
from pip._vendor.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_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",
]

View File

@@ -17,7 +17,7 @@
import functools
import sys
import typing
import typing as t
from asyncio import sleep
from pip._vendor.tenacity import AttemptManager
@@ -26,21 +26,20 @@ from pip._vendor.tenacity import DoAttempt
from pip._vendor.tenacity import DoSleep
from pip._vendor.tenacity import RetryCallState
WrappedFn = typing.TypeVar("WrappedFn", bound=typing.Callable)
_RetValT = typing.TypeVar("_RetValT")
WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
class AsyncRetrying(BaseRetrying):
def __init__(self, sleep: typing.Callable[[float], typing.Awaitable] = sleep, **kwargs: typing.Any) -> None:
sleep: t.Callable[[float], t.Awaitable[t.Any]]
def __init__(self, sleep: t.Callable[[float], t.Awaitable[t.Any]] = sleep, **kwargs: t.Any) -> None:
super().__init__(**kwargs)
self.sleep = sleep
async def __call__( # type: ignore # Change signature from supertype
self,
fn: typing.Callable[..., typing.Awaitable[_RetValT]],
*args: typing.Any,
**kwargs: typing.Any,
) -> _RetValT:
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)
@@ -50,21 +49,24 @@ class AsyncRetrying(BaseRetrying):
try:
result = await fn(*args, **kwargs)
except BaseException: # noqa: B902
retry_state.set_exception(sys.exc_info())
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)
else:
return do
return do # type: ignore[no-any-return]
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) -> typing.Union[AttemptManager, typing.Any]:
async def __anext__(self) -> AttemptManager:
while True:
do = self.iter(retry_state=self._retry_state)
if do is None:
@@ -75,18 +77,18 @@ class AsyncRetrying(BaseRetrying):
self._retry_state.prepare_for_next_attempt()
await self.sleep(do)
else:
return do
raise StopAsyncIteration
def wraps(self, fn: WrappedFn) -> WrappedFn:
fn = super().wraps(fn)
# Ensure wrapper is recognized as a coroutine function.
@functools.wraps(fn)
async def async_wrapped(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
return await fn(*args, **kwargs)
# Preserve attributes
async_wrapped.retry = fn.retry
async_wrapped.retry_with = fn.retry_with
async_wrapped.retry = fn.retry # type: ignore[attr-defined]
async_wrapped.retry_with = fn.retry_with # type: ignore[attr-defined]
return async_wrapped
return async_wrapped # type: ignore[return-value]

View File

@@ -16,6 +16,7 @@
import sys
import typing
from datetime import timedelta
# sys.maxsize:
@@ -66,3 +67,10 @@ def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str:
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)

View File

@@ -36,9 +36,14 @@ def after_log(
"""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 '{_utils.get_callback_name(retry_state.fn)}' "
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.",
)

View File

@@ -32,9 +32,14 @@ def before_log(logger: "logging.Logger", log_level: int) -> typing.Callable[["Re
"""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 '{_utils.get_callback_name(retry_state.fn)}', "
f"Starting call to '{fn_name}', "
f"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
)

View File

@@ -36,6 +36,14 @@ def before_sleep_log(
"""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}"
@@ -48,10 +56,15 @@ def before_sleep_log(
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 {_utils.get_callback_name(retry_state.fn)} "
f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
f"Retrying {fn_name} " f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
exc_info=local_exc_info,
)

View File

@@ -36,6 +36,9 @@ class retry_base(abc.ABC):
return retry_any(self, other)
RetryBaseT = typing.Union[retry_base, typing.Callable[["RetryCallState"], bool]]
class _retry_never(retry_base):
"""Retry strategy that never rejects any result."""
@@ -63,8 +66,14 @@ class retry_if_exception(retry_base):
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:
return self.predicate(retry_state.outcome.exception())
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
@@ -111,10 +120,47 @@ class retry_unless_exception_type(retry_if_exception):
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
return self.predicate(retry_state.outcome.exception())
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):
@@ -124,6 +170,9 @@ class retry_if_result(retry_base):
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:
@@ -137,6 +186,9 @@ class retry_if_not_result(retry_base):
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:
@@ -188,9 +240,16 @@ class retry_if_not_exception_message(retry_if_exception_message):
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
return self.predicate(retry_state.outcome.exception())
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):

View File

@@ -16,6 +16,8 @@
import abc
import typing
from pip._vendor.tenacity import _utils
if typing.TYPE_CHECKING:
import threading
@@ -36,6 +38,9 @@ class stop_base(abc.ABC):
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."""
@@ -89,8 +94,10 @@ class stop_after_attempt(stop_base):
class stop_after_delay(stop_base):
"""Stop when the time from the first attempt >= limit."""
def __init__(self, max_delay: float) -> None:
self.max_delay = max_delay
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

View File

@@ -33,8 +33,8 @@ class TornadoRetrying(BaseRetrying):
super().__init__(**kwargs)
self.sleep = sleep
@gen.coroutine
def __call__( # type: ignore # Change signature from supertype
@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,
@@ -49,7 +49,7 @@ class TornadoRetrying(BaseRetrying):
try:
result = yield fn(*args, **kwargs)
except BaseException: # noqa: B902
retry_state.set_exception(sys.exc_info())
retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type]
else:
retry_state.set_result(result)
elif isinstance(do, DoSleep):

View File

@@ -36,16 +36,19 @@ class wait_base(abc.ABC):
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:
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: float) -> None:
self.wait_fixed = wait
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
@@ -61,9 +64,9 @@ class wait_none(wait_fixed):
class wait_random(wait_base):
"""Wait strategy that waits a random amount of time between min/max."""
def __init__(self, min: typing.Union[int, float] = 0, max: typing.Union[int, float] = 1) -> None: # noqa
self.wait_random_min = min
self.wait_random_max = 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))
@@ -113,13 +116,13 @@ class wait_incrementing(wait_base):
def __init__(
self,
start: typing.Union[int, float] = 0,
increment: typing.Union[int, float] = 100,
max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa
start: _utils.time_unit_type = 0,
increment: _utils.time_unit_type = 100,
max: _utils.time_unit_type = _utils.MAX_WAIT, # noqa
) -> None:
self.start = start
self.increment = increment
self.max = max
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))
@@ -142,13 +145,13 @@ class wait_exponential(wait_base):
def __init__(
self,
multiplier: typing.Union[int, float] = 1,
max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa
max: _utils.time_unit_type = _utils.MAX_WAIT, # noqa
exp_base: typing.Union[int, float] = 2,
min: typing.Union[int, float] = 0, # noqa
min: _utils.time_unit_type = 0, # noqa
) -> None:
self.multiplier = multiplier
self.min = min
self.max = max
self.min = _utils.to_seconds(min)
self.max = _utils.to_seconds(max)
self.exp_base = exp_base
def __call__(self, retry_state: "RetryCallState") -> float:
@@ -189,3 +192,37 @@ class wait_random_exponential(wait_exponential):
def __call__(self, retry_state: "RetryCallState") -> float:
high = super().__call__(retry_state=retry_state)
return random.uniform(0, 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))