Cleaned up the directories
This commit is contained in:
parent
f708506d68
commit
a683fcffea
1340 changed files with 554582 additions and 6840 deletions
|
@ -0,0 +1,608 @@
|
|||
# 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 functools
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import typing as t
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from concurrent import futures
|
||||
from inspect import iscoroutinefunction
|
||||
|
||||
# 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_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
|
||||
|
||||
# Replace a conditional import with a hard-coded None so that pip does
|
||||
# not attempt to use tornado even if it is present in the environment.
|
||||
# If tornado is non-None, tenacity will attempt to execute some code
|
||||
# that is sensitive to the version of tornado, which could break pip
|
||||
# if an old version is found.
|
||||
tornado = None # type: ignore
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import types
|
||||
|
||||
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])
|
||||
|
||||
|
||||
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
|
||||
|
||||
def wraps(self, f: WrappedFn) -> WrappedFn:
|
||||
"""Wrap a function for retrying.
|
||||
|
||||
:param f: A function to wraps for retrying.
|
||||
"""
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
|
||||
return self(f, *args, **kw)
|
||||
|
||||
def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn:
|
||||
return self.copy(*args, **kwargs).wraps(f)
|
||||
|
||||
wrapped_f.retry = self # type: ignore[attr-defined]
|
||||
wrapped_f.retry_with = retry_with # 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 iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa
|
||||
fut = retry_state.outcome
|
||||
if fut is None:
|
||||
if self.before is not None:
|
||||
self.before(retry_state)
|
||||
return DoAttempt()
|
||||
|
||||
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):
|
||||
if self.retry_error_callback:
|
||||
return self.retry_error_callback(retry_state)
|
||||
retry_exc = self.retry_error_cls(fut)
|
||||
if self.reraise:
|
||||
raise retry_exc.reraise()
|
||||
raise retry_exc from fut.exception()
|
||||
|
||||
if self.wait:
|
||||
sleep = self.wait(retry_state)
|
||||
else:
|
||||
sleep = 0.0
|
||||
retry_state.next_action = RetryAction(sleep)
|
||||
retry_state.idle_for += sleep
|
||||
self.statistics["idle_for"] += sleep
|
||||
self.statistics["attempt_number"] += 1
|
||||
|
||||
if self.before_sleep is not None:
|
||||
self.before_sleep(retry_state)
|
||||
|
||||
return DoSleep(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[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:
|
||||
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
|
||||
|
||||
@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]], 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",
|
||||
]
|
|
@ -0,0 +1,94 @@
|
|||
# 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
|
||||
from asyncio import sleep
|
||||
|
||||
from pip._vendor.tenacity import AttemptManager
|
||||
from pip._vendor.tenacity import BaseRetrying
|
||||
from pip._vendor.tenacity import DoAttempt
|
||||
from pip._vendor.tenacity import DoSleep
|
||||
from pip._vendor.tenacity import RetryCallState
|
||||
|
||||
WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
|
||||
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
|
||||
|
||||
|
||||
class AsyncRetrying(BaseRetrying):
|
||||
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[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 = 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)
|
||||
else:
|
||||
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) -> AttemptManager:
|
||||
while True:
|
||||
do = 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)
|
||||
else:
|
||||
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: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
return await fn(*args, **kwargs)
|
||||
|
||||
# Preserve attributes
|
||||
async_wrapped.retry = fn.retry # type: ignore[attr-defined]
|
||||
async_wrapped.retry_with = fn.retry_with # type: ignore[attr-defined]
|
||||
|
||||
return async_wrapped # type: ignore[return-value]
|
|
@ -0,0 +1,76 @@
|
|||
# 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 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)
|
|
@ -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 pip._vendor.tenacity import _utils
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import logging
|
||||
|
||||
from pip._vendor.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
|
|
@ -0,0 +1,46 @@
|
|||
# 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 pip._vendor.tenacity import _utils
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import logging
|
||||
|
||||
from pip._vendor.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
|
|
@ -0,0 +1,71 @@
|
|||
# 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 pip._vendor.tenacity import _utils
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import logging
|
||||
|
||||
from pip._vendor.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
|
|
@ -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)
|
|
@ -0,0 +1,272 @@
|
|||
# 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 pip._vendor.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 retry_all(self, other)
|
||||
|
||||
def __or__(self, other: "retry_base") -> "retry_any":
|
||||
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."""
|
||||
|
||||
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)
|
|
@ -0,0 +1,103 @@
|
|||
# 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 pip._vendor.tenacity import _utils
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import threading
|
||||
|
||||
from pip._vendor.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."""
|
||||
|
||||
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
|
|
@ -0,0 +1,59 @@
|
|||
# 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 pip._vendor.tenacity import BaseRetrying
|
||||
from pip._vendor.tenacity import DoAttempt
|
||||
from pip._vendor.tenacity import DoSleep
|
||||
from pip._vendor.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)
|
|
@ -0,0 +1,228 @@
|
|||
# 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 pip._vendor.tenacity import _utils
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from pip._vendor.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(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))
|
Loading…
Add table
Add a link
Reference in a new issue