switch from 'retrying' to 'tenacity'

This commit is contained in:
cjc7373 2021-02-10 21:48:53 +08:00
parent 8a949a1c52
commit 64ecfc8476
23 changed files with 1786 additions and 279 deletions

1
news/tenacity.vendor.rst Normal file
View File

@ -0,0 +1 @@
Switch from retrying to tenacity

View File

@ -44,6 +44,8 @@ drop = [
"setuptools",
"pkg_resources/_vendor/",
"pkg_resources/extern/",
# unneeded part for tenacity
"tenacity/tests",
]
[tool.vendoring.typing-stubs]

View File

@ -9,9 +9,7 @@ from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from typing import Any, BinaryIO, Iterator, List, Union, cast
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
# why we ignore the type on this import.
from pip._vendor.retrying import retry # type: ignore
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
from pip._internal.utils.compat import get_path_uid
from pip._internal.utils.misc import format_size
@ -101,7 +99,8 @@ def adjacent_tmp_file(path, **kwargs):
os.fsync(result.fileno())
_replace_retry = retry(stop_max_delay=1000, wait_fixed=250)
# Tenacity raises RetryError by default, explictly raise the original exception
_replace_retry = retry(reraise=True, stop=stop_after_delay(1), wait=wait_fixed(0.25))
replace = _replace_retry(os.replace)

View File

@ -31,10 +31,7 @@ from typing import (
)
from pip._vendor.pkg_resources import Distribution
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
# why we ignore the type on this import.
from pip._vendor.retrying import retry # type: ignore
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
from pip import __version__
from pip._internal.exceptions import CommandError
@ -117,7 +114,8 @@ def get_prog():
# Retry every half second for up to 3 seconds
@retry(stop_max_delay=3000, wait_fixed=500)
# Tenacity raises RetryError by default, explictly raise the original exception
@retry(reraise=True, stop=stop_after_delay(3), wait=wait_fixed(0.5))
def rmtree(dir, ignore_errors=False):
# type: (AnyStr, bool) -> None
shutil.rmtree(dir, ignore_errors=ignore_errors,

View File

@ -75,7 +75,6 @@ if DEBUNDLED:
vendored("pep517")
vendored("pkg_resources")
vendored("progress")
vendored("retrying")
vendored("requests")
vendored("requests.exceptions")
vendored("requests.packages")
@ -107,6 +106,7 @@ if DEBUNDLED:
vendored("requests.packages.urllib3.util.timeout")
vendored("requests.packages.urllib3.util.url")
vendored("resolvelib")
vendored("tenacity")
vendored("toml")
vendored("toml.encoder")
vendored("toml.decoder")

View File

@ -1,267 +0,0 @@
## 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 random
from pip._vendor import six
import sys
import time
import traceback
# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint...
MAX_WAIT = 1073741823
def retry(*dargs, **dkw):
"""
Decorator function that instantiates the 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]):
def wrap_simple(f):
@six.wraps(f)
def wrapped_f(*args, **kw):
return Retrying().call(f, *args, **kw)
return wrapped_f
return wrap_simple(dargs[0])
else:
def wrap(f):
@six.wraps(f)
def wrapped_f(*args, **kw):
return Retrying(*dargs, **dkw).call(f, *args, **kw)
return wrapped_f
return wrap
class Retrying(object):
def __init__(self,
stop=None, wait=None,
stop_max_attempt_number=None,
stop_max_delay=None,
wait_fixed=None,
wait_random_min=None, wait_random_max=None,
wait_incrementing_start=None, wait_incrementing_increment=None,
wait_exponential_multiplier=None, wait_exponential_max=None,
retry_on_exception=None,
retry_on_result=None,
wrap_exception=False,
stop_func=None,
wait_func=None,
wait_jitter_max=None):
self._stop_max_attempt_number = 5 if stop_max_attempt_number is None else stop_max_attempt_number
self._stop_max_delay = 100 if stop_max_delay is None else stop_max_delay
self._wait_fixed = 1000 if wait_fixed is None else wait_fixed
self._wait_random_min = 0 if wait_random_min is None else wait_random_min
self._wait_random_max = 1000 if wait_random_max is None else wait_random_max
self._wait_incrementing_start = 0 if wait_incrementing_start is None else wait_incrementing_start
self._wait_incrementing_increment = 100 if wait_incrementing_increment is None else wait_incrementing_increment
self._wait_exponential_multiplier = 1 if wait_exponential_multiplier is None else wait_exponential_multiplier
self._wait_exponential_max = MAX_WAIT if wait_exponential_max is None else wait_exponential_max
self._wait_jitter_max = 0 if wait_jitter_max is None else wait_jitter_max
# TODO add chaining of stop behaviors
# stop behavior
stop_funcs = []
if stop_max_attempt_number is not None:
stop_funcs.append(self.stop_after_attempt)
if stop_max_delay is not None:
stop_funcs.append(self.stop_after_delay)
if stop_func is not None:
self.stop = stop_func
elif stop is None:
self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs)
else:
self.stop = getattr(self, stop)
# TODO add chaining of wait behaviors
# wait behavior
wait_funcs = [lambda *args, **kwargs: 0]
if wait_fixed is not None:
wait_funcs.append(self.fixed_sleep)
if wait_random_min is not None or wait_random_max is not None:
wait_funcs.append(self.random_sleep)
if wait_incrementing_start is not None or wait_incrementing_increment is not None:
wait_funcs.append(self.incrementing_sleep)
if wait_exponential_multiplier is not None or wait_exponential_max is not None:
wait_funcs.append(self.exponential_sleep)
if wait_func is not None:
self.wait = wait_func
elif wait is None:
self.wait = lambda attempts, delay: max(f(attempts, delay) for f in wait_funcs)
else:
self.wait = getattr(self, wait)
# retry on exception filter
if retry_on_exception is None:
self._retry_on_exception = self.always_reject
else:
self._retry_on_exception = retry_on_exception
# TODO simplify retrying by Exception types
# retry on result filter
if retry_on_result is None:
self._retry_on_result = self.never_reject
else:
self._retry_on_result = retry_on_result
self._wrap_exception = wrap_exception
def stop_after_attempt(self, previous_attempt_number, delay_since_first_attempt_ms):
"""Stop after the previous attempt >= stop_max_attempt_number."""
return previous_attempt_number >= self._stop_max_attempt_number
def stop_after_delay(self, previous_attempt_number, delay_since_first_attempt_ms):
"""Stop after the time from the first attempt >= stop_max_delay."""
return delay_since_first_attempt_ms >= self._stop_max_delay
def no_sleep(self, previous_attempt_number, delay_since_first_attempt_ms):
"""Don't sleep at all before retrying."""
return 0
def fixed_sleep(self, previous_attempt_number, delay_since_first_attempt_ms):
"""Sleep a fixed amount of time between each retry."""
return self._wait_fixed
def random_sleep(self, previous_attempt_number, delay_since_first_attempt_ms):
"""Sleep a random amount of time between wait_random_min and wait_random_max"""
return random.randint(self._wait_random_min, self._wait_random_max)
def incrementing_sleep(self, previous_attempt_number, delay_since_first_attempt_ms):
"""
Sleep an incremental amount of time after each attempt, starting at
wait_incrementing_start and incrementing by wait_incrementing_increment
"""
result = self._wait_incrementing_start + (self._wait_incrementing_increment * (previous_attempt_number - 1))
if result < 0:
result = 0
return result
def exponential_sleep(self, previous_attempt_number, delay_since_first_attempt_ms):
exp = 2 ** previous_attempt_number
result = self._wait_exponential_multiplier * exp
if result > self._wait_exponential_max:
result = self._wait_exponential_max
if result < 0:
result = 0
return result
def never_reject(self, result):
return False
def always_reject(self, result):
return True
def should_reject(self, attempt):
reject = False
if attempt.has_exception:
reject |= self._retry_on_exception(attempt.value[1])
else:
reject |= self._retry_on_result(attempt.value)
return reject
def call(self, fn, *args, **kwargs):
start_time = int(round(time.time() * 1000))
attempt_number = 1
while True:
try:
attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
except:
tb = sys.exc_info()
attempt = Attempt(tb, attempt_number, True)
if not self.should_reject(attempt):
return attempt.get(self._wrap_exception)
delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time
if self.stop(attempt_number, delay_since_first_attempt_ms):
if not self._wrap_exception and attempt.has_exception:
# get() on an attempt with an exception should cause it to be raised, but raise just in case
raise attempt.get()
else:
raise RetryError(attempt)
else:
sleep = self.wait(attempt_number, delay_since_first_attempt_ms)
if self._wait_jitter_max:
jitter = random.random() * self._wait_jitter_max
sleep = sleep + max(0, jitter)
time.sleep(sleep / 1000.0)
attempt_number += 1
class Attempt(object):
"""
An Attempt encapsulates a call to a target function that may end as a
normal return value from the function or an Exception depending on what
occurred during the execution.
"""
def __init__(self, value, attempt_number, has_exception):
self.value = value
self.attempt_number = attempt_number
self.has_exception = has_exception
def get(self, wrap_exception=False):
"""
Return the return value of this Attempt instance or raise an Exception.
If wrap_exception is true, this Attempt is wrapped inside of a
RetryError before being raised.
"""
if self.has_exception:
if wrap_exception:
raise RetryError(self)
else:
six.reraise(self.value[0], self.value[1], self.value[2])
else:
return self.value
def __repr__(self):
if self.has_exception:
return "Attempts: {0}, Error:\n{1}".format(self.attempt_number, "".join(traceback.format_tb(self.value[2])))
else:
return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value)
class RetryError(Exception):
"""
A RetryError encapsulates the last Attempt instance right before giving up.
"""
def __init__(self, last_attempt):
self.last_attempt = last_attempt
def __str__(self):
return "RetryError[{0}]".format(self.last_attempt)

View File

@ -1 +0,0 @@
from retrying import *

View File

@ -0,0 +1 @@
from tenacity import *

View File

@ -0,0 +1,516 @@
# -*- coding: utf-8 -*-
# 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.
try:
from inspect import iscoroutinefunction
except ImportError:
iscoroutinefunction = None
try:
import tornado
except ImportError:
tornado = None
import sys
import threading
import typing as t
import warnings
from abc import ABCMeta, abstractmethod
from concurrent import futures
from pip._vendor import six
from pip._vendor.tenacity import _utils
from pip._vendor.tenacity import compat as _compat
# Import all built-in retry strategies for easier usage.
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_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
# 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
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable)
@t.overload
def retry(fn):
# type: (WrappedFn) -> WrappedFn
"""Type signature for @retry as a raw decorator."""
pass
@t.overload
def retry(*dargs, **dkw): # noqa
# type: (...) -> t.Callable[[WrappedFn], WrappedFn]
"""Type signature for the @retry() decorator constructor."""
pass
def retry(*dargs, **dkw): # 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):
if iscoroutinefunction is not None and 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
class TryAgain(Exception):
"""Always retry the executed function when raised."""
NO_RESULT = object()
class DoAttempt(object):
pass
class DoSleep(float):
pass
class BaseAction(object):
"""Base class for representing actions to take by retry object.
Concrete implementations must define:
- __init__: to initialize all necessary fields
- REPR_ATTRS: class variable specifying attributes to include in repr(self)
- NAME: for identification in retry object methods and callbacks
"""
REPR_FIELDS = ()
NAME = None
def __repr__(self):
state_str = ', '.join('%s=%r' % (field, getattr(self, field))
for field in self.REPR_FIELDS)
return '%s(%s)' % (type(self).__name__, state_str)
def __str__(self):
return repr(self)
class RetryAction(BaseAction):
REPR_FIELDS = ('sleep',)
NAME = 'retry'
def __init__(self, sleep):
self.sleep = float(sleep)
_unset = object()
class RetryError(Exception):
"""Encapsulates the last attempt instance right before giving up."""
def __init__(self, last_attempt):
self.last_attempt = last_attempt
super(RetryError, self).__init__(last_attempt)
def reraise(self):
if self.last_attempt.failed:
raise self.last_attempt.result()
raise self
def __str__(self):
return "{0}[{1}]".format(self.__class__.__name__, self.last_attempt)
class AttemptManager(object):
"""Manage attempt context."""
def __init__(self, retry_state):
self.retry_state = retry_state
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
if isinstance(exc_value, BaseException):
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)
class BaseRetrying(object):
__metaclass__ = ABCMeta
def __init__(self,
sleep=sleep,
stop=stop_never, wait=wait_none(),
retry=retry_if_exception_type(),
before=before_nothing,
after=after_nothing,
before_sleep=None,
reraise=False,
retry_error_cls=RetryError,
retry_error_callback=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
# This attribute was moved to RetryCallState and is deprecated on
# Retrying objects but kept for backward compatibility.
self.fn = None
@_utils.cached_property
def stop(self):
return _compat.stop_func_accept_retry_state(self._stop)
@_utils.cached_property
def wait(self):
return _compat.wait_func_accept_retry_state(self._wait)
@_utils.cached_property
def retry(self):
return _compat.retry_func_accept_retry_state(self._retry)
@_utils.cached_property
def before(self):
return _compat.before_func_accept_retry_state(self._before)
@_utils.cached_property
def after(self):
return _compat.after_func_accept_retry_state(self._after)
@_utils.cached_property
def before_sleep(self):
return _compat.before_sleep_func_accept_retry_state(self._before_sleep)
@_utils.cached_property
def retry_error_callback(self):
return _compat.retry_error_callback_accept_retry_state(
self._retry_error_callback)
def copy(self, sleep=_unset, stop=_unset, wait=_unset,
retry=_unset, before=_unset, after=_unset, before_sleep=_unset,
reraise=_unset):
"""Copy this object with some parameters changed if needed."""
if before_sleep is _unset:
before_sleep = self.before_sleep
return self.__class__(
sleep=self.sleep if sleep is _unset else sleep,
stop=self.stop if stop is _unset else stop,
wait=self.wait if wait is _unset else wait,
retry=self.retry if retry is _unset else retry,
before=self.before if before is _unset else before,
after=self.after if after is _unset else after,
before_sleep=before_sleep,
reraise=self.reraise if after is _unset else reraise,
)
def __repr__(self):
attrs = dict(
_utils.visible_attrs(self, attrs={'me': id(self)}),
__class__=self.__class__.__name__,
)
return ("<%(__class__)s object at 0x%(me)x (stop=%(stop)s, "
"wait=%(wait)s, sleep=%(sleep)s, retry=%(retry)s, "
"before=%(before)s, after=%(after)s)>") % (attrs)
@property
def statistics(self):
"""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
except AttributeError:
self._local.statistics = {}
return self._local.statistics
def wraps(self, f):
"""Wrap a function for retrying.
:param f: A function to wraps for retrying.
"""
@_utils.wraps(f)
def wrapped_f(*args, **kw):
return self(f, *args, **kw)
def retry_with(*args, **kwargs):
return self.copy(*args, **kwargs).wraps(f)
wrapped_f.retry = self
wrapped_f.retry_with = retry_with
return wrapped_f
def begin(self, fn):
self.statistics.clear()
self.statistics['start_time'] = _utils.now()
self.statistics['attempt_number'] = 1
self.statistics['idle_for'] = 0
self.fn = fn
def iter(self, retry_state): # noqa
fut = retry_state.outcome
if fut is None:
if self.before is not None:
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)):
return fut.result()
if self.after is not None:
self.after(retry_state=retry_state)
self.statistics['delay_since_first_attempt'] = \
retry_state.seconds_since_start
if self.stop(retry_state=retry_state):
if self.retry_error_callback:
return self.retry_error_callback(retry_state=retry_state)
retry_exc = self.retry_error_cls(fut)
if self.reraise:
raise retry_exc.reraise()
six.raise_from(retry_exc, fut.exception())
if self.wait:
sleep = self.wait(retry_state=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=retry_state)
return DoSleep(sleep)
def __iter__(self):
self.begin(None)
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, *args, **kwargs):
pass
def call(self, *args, **kwargs):
"""Use ``__call__`` instead because this method is deprecated."""
warnings.warn("'call()' method is deprecated. " +
"Use '__call__()' instead", DeprecationWarning)
return self.__call__(*args, **kwargs)
class Retrying(BaseRetrying):
"""Retrying controller."""
def __call__(self, fn, *args, **kwargs):
self.begin(fn)
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:
retry_state.set_exception(sys.exc_info())
else:
retry_state.set_result(result)
elif isinstance(do, DoSleep):
retry_state.prepare_for_next_attempt()
self.sleep(do)
else:
return do
class Future(futures.Future):
"""Encapsulates a (future or past) attempted call to a target function."""
def __init__(self, attempt_number):
super(Future, self).__init__()
self.attempt_number = attempt_number
@property
def failed(self):
"""Return whether a exception is being held in this future."""
return self.exception() is not None
@classmethod
def construct(cls, attempt_number, value, has_exception):
"""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(object):
"""State related to a single call wrapped with Retrying."""
def __init__(self, retry_object, fn, args, kwargs):
#: Retry call start timestamp
self.start_time = _utils.now()
#: 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 = 1
#: Last outcome (result or exception) produced by the function
self.outcome = None
#: Timestamp of the last outcome
self.outcome_timestamp = None
#: Time spent sleeping in retries
self.idle_for = 0
#: Next action as decided by the retry manager
self.next_action = None
@property
def seconds_since_start(self):
if self.outcome_timestamp is None:
return None
return self.outcome_timestamp - self.start_time
def prepare_for_next_attempt(self):
self.outcome = None
self.outcome_timestamp = None
self.attempt_number += 1
self.next_action = None
def set_result(self, val):
ts = _utils.now()
fut = Future(self.attempt_number)
fut.set_result(val)
self.outcome, self.outcome_timestamp = fut, ts
def set_exception(self, exc_info):
ts = _utils.now()
fut = Future(self.attempt_number)
_utils.capture(fut, exc_info)
self.outcome, self.outcome_timestamp = fut, ts
if iscoroutinefunction:
from pip._vendor.tenacity._asyncio import AsyncRetrying
if tornado:
from pip._vendor.tenacity.tornadoweb import TornadoRetrying

View File

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# 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 sys
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
class AsyncRetrying(BaseRetrying):
def __init__(self,
sleep=sleep,
**kwargs):
super(AsyncRetrying, self).__init__(**kwargs)
self.sleep = sleep
async def __call__(self, fn, *args, **kwargs):
self.begin(fn)
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:
retry_state.set_exception(sys.exc_info())
else:
retry_state.set_result(result)
elif isinstance(do, DoSleep):
retry_state.prepare_for_next_attempt()
await self.sleep(do)
else:
return do
def __aiter__(self):
self.begin(None)
self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
return self
async def __anext__(self):
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:
return do
def wraps(self, fn):
fn = super().wraps(fn)
# Ensure wrapper is recognized as a coroutine function.
async def async_wrapped(*args, **kwargs):
return await fn(*args, **kwargs)
# Preserve attributes
async_wrapped.retry = fn.retry
async_wrapped.retry_with = fn.retry_with
return async_wrapped

View File

@ -0,0 +1,154 @@
# 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 inspect
import sys
import time
from functools import update_wrapper
from pip._vendor import six
# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint...
try:
MAX_WAIT = sys.maxint / 2
except AttributeError:
MAX_WAIT = 1073741823
if six.PY2:
from functools import WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
def wraps(fn):
"""Do the same as six.wraps but only copy attributes that exist.
For example, object instances don't have __name__ attribute, so
six.wraps fails. This is fixed in Python 3
(https://bugs.python.org/issue3445), but didn't get backported to six.
Also, see https://github.com/benjaminp/six/issues/250.
"""
def filter_hasattr(obj, attrs):
return tuple(a for a in attrs if hasattr(obj, a))
return six.wraps(
fn,
assigned=filter_hasattr(fn, WRAPPER_ASSIGNMENTS),
updated=filter_hasattr(fn, WRAPPER_UPDATES))
def capture(fut, tb):
# TODO(harlowja): delete this in future, since its
# has to repeatedly calculate this crap.
fut.set_exception_info(tb[1], tb[2])
def getargspec(func):
# This was deprecated in Python 3.
return inspect.getargspec(func)
else:
from functools import wraps # noqa
def capture(fut, tb):
fut.set_exception(tb[1])
def getargspec(func):
return inspect.getfullargspec(func)
def visible_attrs(obj, attrs=None):
if attrs is None:
attrs = {}
for attr_name, attr in inspect.getmembers(obj):
if attr_name.startswith("_"):
continue
attrs[attr_name] = attr
return attrs
def find_ordinal(pos_num):
# 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 pos_num >= 4 and pos_num <= 20:
return 'th'
else:
return find_ordinal(pos_num % 10)
def to_ordinal(pos_num):
return "%i%s" % (pos_num, find_ordinal(pos_num))
def get_callback_name(cb):
"""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__)
if inspect.ismethod(cb):
try:
# This attribute doesn't exist on py3.x or newer, so
# we optionally ignore it... (on those versions of
# python `__qualname__` should have been found anyway).
segments.insert(0, cb.im_class.__name__)
except AttributeError:
pass
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)
try:
now = time.monotonic # noqa
except AttributeError:
from monotonic import monotonic as now # noqa
class cached_property(object):
"""A property that is computed once per instance.
Upon being computed it replaces itself with an ordinary attribute. Deleting
the attribute resets the property.
Source: https://github.com/bottlepy/bottle/blob/1de24157e74a6971d136550afe1b63eec5b0df2b/bottle.py#L234-L246
""" # noqa: E501
def __init__(self, func):
update_wrapper(self, func)
self.func = func
def __get__(self, obj, cls):
if obj is None:
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value

View File

@ -0,0 +1,35 @@
# 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.
from pip._vendor.tenacity import _utils
def after_nothing(retry_state):
"""After call strategy that does nothing."""
def after_log(logger, log_level, sec_format="%0.3f"):
"""After call strategy that logs to some logger the finished attempt."""
log_tpl = ("Finished call to '%s' after " + str(sec_format) + "(s), "
"this was the %s time calling it.")
def log_it(retry_state):
logger.log(log_level, log_tpl,
_utils.get_callback_name(retry_state.fn),
retry_state.seconds_since_start,
_utils.to_ordinal(retry_state.attempt_number))
return log_it

View File

@ -0,0 +1,32 @@
# 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.
from pip._vendor.tenacity import _utils
def before_nothing(retry_state):
"""Before call strategy that does nothing."""
def before_log(logger, log_level):
"""Before call strategy that logs to some logger the attempt."""
def log_it(retry_state):
logger.log(log_level,
"Starting call to '%s', this is the %s time calling it.",
_utils.get_callback_name(retry_state.fn),
_utils.to_ordinal(retry_state.attempt_number))
return log_it

View File

@ -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.
from pip._vendor.tenacity import _utils
from pip._vendor.tenacity.compat import get_exc_info_from_future
def before_sleep_nothing(retry_state):
"""Before call strategy that does nothing."""
def before_sleep_log(logger, log_level, exc_info=False):
"""Before call strategy that logs to some logger the attempt."""
def log_it(retry_state):
if retry_state.outcome.failed:
ex = retry_state.outcome.exception()
verb, value = 'raised', '%s: %s' % (type(ex).__name__, ex)
if exc_info:
local_exc_info = get_exc_info_from_future(retry_state.outcome)
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
logger.log(log_level,
"Retrying %s in %s seconds as it %s %s.",
_utils.get_callback_name(retry_state.fn),
getattr(retry_state.next_action, 'sleep'),
verb, value,
exc_info=local_exc_info)
return log_it

View File

@ -0,0 +1,322 @@
"""Utilities for providing backward compatibility."""
import inspect
from fractions import Fraction
from warnings import warn
from pip._vendor import six
from pip._vendor.tenacity import _utils
def warn_about_non_retry_state_deprecation(cbname, func, stacklevel):
msg = (
'"%s" function must accept single "retry_state" parameter,'
' please update %s' % (cbname, _utils.get_callback_name(func)))
warn(msg, DeprecationWarning, stacklevel=stacklevel + 1)
def warn_about_dunder_non_retry_state_deprecation(fn, stacklevel):
msg = (
'"%s" method must be called with'
' single "retry_state" parameter' % (_utils.get_callback_name(fn)))
warn(msg, DeprecationWarning, stacklevel=stacklevel + 1)
def func_takes_retry_state(func):
if not six.callable(func):
raise Exception(func)
return False
if not inspect.isfunction(func) and not inspect.ismethod(func):
# func is a callable object rather than a function/method
func = func.__call__
func_spec = _utils.getargspec(func)
return 'retry_state' in func_spec.args
_unset = object()
def _make_unset_exception(func_name, **kwargs):
missing = []
for k, v in six.iteritems(kwargs):
if v is _unset:
missing.append(k)
missing_str = ', '.join(repr(s) for s in missing)
return TypeError(func_name + ' func missing parameters: ' + missing_str)
def _set_delay_since_start(retry_state, delay):
# Ensure outcome_timestamp - start_time is *exactly* equal to the delay to
# avoid complexity in test code.
retry_state.start_time = Fraction(retry_state.start_time)
retry_state.outcome_timestamp = (retry_state.start_time + Fraction(delay))
assert retry_state.seconds_since_start == delay
def make_retry_state(previous_attempt_number, delay_since_first_attempt,
last_result=None):
"""Construct RetryCallState for given attempt number & delay.
Only used in testing and thus is extra careful about timestamp arithmetics.
"""
required_parameter_unset = (previous_attempt_number is _unset or
delay_since_first_attempt is _unset)
if required_parameter_unset:
raise _make_unset_exception(
'wait/stop',
previous_attempt_number=previous_attempt_number,
delay_since_first_attempt=delay_since_first_attempt)
from pip._vendor.tenacity import RetryCallState
retry_state = RetryCallState(None, None, (), {})
retry_state.attempt_number = previous_attempt_number
if last_result is not None:
retry_state.outcome = last_result
else:
retry_state.set_result(None)
_set_delay_since_start(retry_state, delay_since_first_attempt)
return retry_state
def func_takes_last_result(waiter):
"""Check if function has a "last_result" parameter.
Needed to provide backward compatibility for wait functions that didn't
take "last_result" in the beginning.
"""
if not six.callable(waiter):
return False
if not inspect.isfunction(waiter) and not inspect.ismethod(waiter):
# waiter is a class, check dunder-call rather than dunder-init.
waiter = waiter.__call__
waiter_spec = _utils.getargspec(waiter)
return 'last_result' in waiter_spec.args
def stop_dunder_call_accept_old_params(fn):
"""Decorate cls.__call__ method to accept old "stop" signature."""
@_utils.wraps(fn)
def new_fn(self,
previous_attempt_number=_unset,
delay_since_first_attempt=_unset,
retry_state=None):
if retry_state is None:
from pip._vendor.tenacity import RetryCallState
retry_state_passed_as_non_kwarg = (
previous_attempt_number is not _unset and
isinstance(previous_attempt_number, RetryCallState))
if retry_state_passed_as_non_kwarg:
retry_state = previous_attempt_number
else:
warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2)
retry_state = make_retry_state(
previous_attempt_number=previous_attempt_number,
delay_since_first_attempt=delay_since_first_attempt)
return fn(self, retry_state=retry_state)
return new_fn
def stop_func_accept_retry_state(stop_func):
"""Wrap "stop" function to accept "retry_state" parameter."""
if not six.callable(stop_func):
return stop_func
if func_takes_retry_state(stop_func):
return stop_func
@_utils.wraps(stop_func)
def wrapped_stop_func(retry_state):
warn_about_non_retry_state_deprecation(
'stop', stop_func, stacklevel=4)
return stop_func(
retry_state.attempt_number,
retry_state.seconds_since_start,
)
return wrapped_stop_func
def wait_dunder_call_accept_old_params(fn):
"""Decorate cls.__call__ method to accept old "wait" signature."""
@_utils.wraps(fn)
def new_fn(self,
previous_attempt_number=_unset,
delay_since_first_attempt=_unset,
last_result=None,
retry_state=None):
if retry_state is None:
from pip._vendor.tenacity import RetryCallState
retry_state_passed_as_non_kwarg = (
previous_attempt_number is not _unset and
isinstance(previous_attempt_number, RetryCallState))
if retry_state_passed_as_non_kwarg:
retry_state = previous_attempt_number
else:
warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2)
retry_state = make_retry_state(
previous_attempt_number=previous_attempt_number,
delay_since_first_attempt=delay_since_first_attempt,
last_result=last_result)
return fn(self, retry_state=retry_state)
return new_fn
def wait_func_accept_retry_state(wait_func):
"""Wrap wait function to accept "retry_state" parameter."""
if not six.callable(wait_func):
return wait_func
if func_takes_retry_state(wait_func):
return wait_func
if func_takes_last_result(wait_func):
@_utils.wraps(wait_func)
def wrapped_wait_func(retry_state):
warn_about_non_retry_state_deprecation(
'wait', wait_func, stacklevel=4)
return wait_func(
retry_state.attempt_number,
retry_state.seconds_since_start,
last_result=retry_state.outcome,
)
else:
@_utils.wraps(wait_func)
def wrapped_wait_func(retry_state):
warn_about_non_retry_state_deprecation(
'wait', wait_func, stacklevel=4)
return wait_func(
retry_state.attempt_number,
retry_state.seconds_since_start,
)
return wrapped_wait_func
def retry_dunder_call_accept_old_params(fn):
"""Decorate cls.__call__ method to accept old "retry" signature."""
@_utils.wraps(fn)
def new_fn(self, attempt=_unset, retry_state=None):
if retry_state is None:
from pip._vendor.tenacity import RetryCallState
if attempt is _unset:
raise _make_unset_exception('retry', attempt=attempt)
retry_state_passed_as_non_kwarg = (
attempt is not _unset and
isinstance(attempt, RetryCallState))
if retry_state_passed_as_non_kwarg:
retry_state = attempt
else:
warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2)
retry_state = RetryCallState(None, None, (), {})
retry_state.outcome = attempt
return fn(self, retry_state=retry_state)
return new_fn
def retry_func_accept_retry_state(retry_func):
"""Wrap "retry" function to accept "retry_state" parameter."""
if not six.callable(retry_func):
return retry_func
if func_takes_retry_state(retry_func):
return retry_func
@_utils.wraps(retry_func)
def wrapped_retry_func(retry_state):
warn_about_non_retry_state_deprecation(
'retry', retry_func, stacklevel=4)
return retry_func(retry_state.outcome)
return wrapped_retry_func
def before_func_accept_retry_state(fn):
"""Wrap "before" function to accept "retry_state"."""
if not six.callable(fn):
return fn
if func_takes_retry_state(fn):
return fn
@_utils.wraps(fn)
def wrapped_before_func(retry_state):
# func, trial_number, trial_time_taken
warn_about_non_retry_state_deprecation('before', fn, stacklevel=4)
return fn(
retry_state.fn,
retry_state.attempt_number,
)
return wrapped_before_func
def after_func_accept_retry_state(fn):
"""Wrap "after" function to accept "retry_state"."""
if not six.callable(fn):
return fn
if func_takes_retry_state(fn):
return fn
@_utils.wraps(fn)
def wrapped_after_sleep_func(retry_state):
# func, trial_number, trial_time_taken
warn_about_non_retry_state_deprecation('after', fn, stacklevel=4)
return fn(
retry_state.fn,
retry_state.attempt_number,
retry_state.seconds_since_start)
return wrapped_after_sleep_func
def before_sleep_func_accept_retry_state(fn):
"""Wrap "before_sleep" function to accept "retry_state"."""
if not six.callable(fn):
return fn
if func_takes_retry_state(fn):
return fn
@_utils.wraps(fn)
def wrapped_before_sleep_func(retry_state):
# retry_object, sleep, last_result
warn_about_non_retry_state_deprecation(
'before_sleep', fn, stacklevel=4)
return fn(
retry_state.retry_object,
sleep=getattr(retry_state.next_action, 'sleep'),
last_result=retry_state.outcome)
return wrapped_before_sleep_func
def retry_error_callback_accept_retry_state(fn):
if not six.callable(fn):
return fn
if func_takes_retry_state(fn):
return fn
@_utils.wraps(fn)
def wrapped_retry_error_callback(retry_state):
warn_about_non_retry_state_deprecation(
'retry_error_callback', fn, stacklevel=4)
return fn(retry_state.outcome)
return wrapped_retry_error_callback
def get_exc_info_from_future(future):
"""
Get an exc_info value from a Future.
Given a a Future instance, retrieve an exc_info value suitable for passing
in as the exc_info parameter to logging.Logger.log() and related methods.
On Python 2, this will be a (type, value, traceback) triple.
On Python 3, this will be an exception instance (with embedded traceback).
If there was no exception, None is returned on both versions of Python.
"""
if six.PY3:
return future.exception()
else:
ex, tb = future.exception_info()
if ex is None:
return None
return type(ex), ex, tb

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# 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
def sleep(seconds):
"""
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(object):
"""Sleep strategy that waits on an event to be set."""
def __init__(self, event):
self.event = event
def __call__(self, timeout):
# 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)

View File

View File

@ -0,0 +1,193 @@
# 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 abc
import re
from pip._vendor import six
from pip._vendor.tenacity import compat as _compat
@six.add_metaclass(abc.ABCMeta)
class retry_base(object):
"""Abstract base class for retry strategies."""
@abc.abstractmethod
def __call__(self, retry_state):
pass
def __and__(self, other):
return retry_all(self, other)
def __or__(self, other):
return retry_any(self, other)
class _retry_never(retry_base):
"""Retry strategy that never rejects any result."""
def __call__(self, retry_state):
return False
retry_never = _retry_never()
class _retry_always(retry_base):
"""Retry strategy that always rejects any result."""
def __call__(self, retry_state):
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):
self.predicate = predicate
@_compat.retry_dunder_call_accept_old_params
def __call__(self, retry_state):
if retry_state.outcome.failed:
return self.predicate(retry_state.outcome.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=Exception):
self.exception_types = exception_types
super(retry_if_exception_type, self).__init__(
lambda e: 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=Exception):
self.exception_types = exception_types
super(retry_unless_exception_type, self).__init__(
lambda e: not isinstance(e, exception_types))
@_compat.retry_dunder_call_accept_old_params
def __call__(self, retry_state):
# always retry if no exception was raised
if not retry_state.outcome.failed:
return True
return self.predicate(retry_state.outcome.exception())
class retry_if_result(retry_base):
"""Retries if the result verifies a predicate."""
def __init__(self, predicate):
self.predicate = predicate
@_compat.retry_dunder_call_accept_old_params
def __call__(self, retry_state):
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):
self.predicate = predicate
@_compat.retry_dunder_call_accept_old_params
def __call__(self, retry_state):
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=None, match=None):
if message and match:
raise TypeError(
"{}() takes either 'message' or 'match', not both".format(
self.__class__.__name__))
# set predicate
if message:
def message_fnc(exception):
return message == str(exception)
predicate = message_fnc
elif match:
prog = re.compile(match)
def match_fnc(exception):
return prog.match(str(exception))
predicate = match_fnc
else:
raise TypeError(
"{}() missing 1 required argument 'message' or 'match'".
format(self.__class__.__name__))
super(retry_if_exception_message, self).__init__(predicate)
class retry_if_not_exception_message(retry_if_exception_message):
"""Retries until an exception message equals or matches."""
def __init__(self, *args, **kwargs):
super(retry_if_not_exception_message, self).__init__(*args, **kwargs)
# invert predicate
if_predicate = self.predicate
self.predicate = lambda *args_, **kwargs_: not if_predicate(
*args_, **kwargs_)
@_compat.retry_dunder_call_accept_old_params
def __call__(self, retry_state):
if not retry_state.outcome.failed:
return True
return self.predicate(retry_state.outcome.exception())
class retry_any(retry_base):
"""Retries if any of the retries condition is valid."""
def __init__(self, *retries):
self.retries = tuple(_compat.retry_func_accept_retry_state(r)
for r in retries)
@_compat.retry_dunder_call_accept_old_params
def __call__(self, retry_state):
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):
self.retries = tuple(_compat.retry_func_accept_retry_state(r)
for r in retries)
@_compat.retry_dunder_call_accept_old_params
def __call__(self, retry_state):
return all(r(retry_state) for r in self.retries)

View File

@ -0,0 +1,103 @@
# 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 abc
from pip._vendor import six
from pip._vendor.tenacity import compat as _compat
@six.add_metaclass(abc.ABCMeta)
class stop_base(object):
"""Abstract base class for stop strategies."""
@abc.abstractmethod
def __call__(self, retry_state):
pass
def __and__(self, other):
return stop_all(self, other)
def __or__(self, other):
return stop_any(self, other)
class stop_any(stop_base):
"""Stop if any of the stop condition is valid."""
def __init__(self, *stops):
self.stops = tuple(_compat.stop_func_accept_retry_state(stop_func)
for stop_func in stops)
@_compat.stop_dunder_call_accept_old_params
def __call__(self, retry_state):
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):
self.stops = tuple(_compat.stop_func_accept_retry_state(stop_func)
for stop_func in stops)
@_compat.stop_dunder_call_accept_old_params
def __call__(self, retry_state):
return all(x(retry_state) for x in self.stops)
class _stop_never(stop_base):
"""Never stop."""
@_compat.stop_dunder_call_accept_old_params
def __call__(self, retry_state):
return False
stop_never = _stop_never()
class stop_when_event_set(stop_base):
"""Stop when the given event is set."""
def __init__(self, event):
self.event = event
@_compat.stop_dunder_call_accept_old_params
def __call__(self, retry_state):
return self.event.is_set()
class stop_after_attempt(stop_base):
"""Stop when the previous attempt >= max_attempt."""
def __init__(self, max_attempt_number):
self.max_attempt_number = max_attempt_number
@_compat.stop_dunder_call_accept_old_params
def __call__(self, retry_state):
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):
self.max_delay = max_delay
@_compat.stop_dunder_call_accept_old_params
def __call__(self, retry_state):
return retry_state.seconds_since_start >= self.max_delay

View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# 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
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
class TornadoRetrying(BaseRetrying):
def __init__(self,
sleep=gen.sleep,
**kwargs):
super(TornadoRetrying, self).__init__(**kwargs)
self.sleep = sleep
@gen.coroutine
def __call__(self, fn, *args, **kwargs):
self.begin(fn)
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:
retry_state.set_exception(sys.exc_info())
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)

View File

@ -0,0 +1,195 @@
# 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 abc
import random
from pip._vendor import six
from pip._vendor.tenacity import _utils
from pip._vendor.tenacity import compat as _compat
@six.add_metaclass(abc.ABCMeta)
class wait_base(object):
"""Abstract base class for wait strategies."""
@abc.abstractmethod
def __call__(self, retry_state):
pass
def __add__(self, other):
return wait_combine(self, other)
def __radd__(self, other):
# make it possible to use multiple waits with the built-in sum function
if other == 0:
return self
return self.__add__(other)
class wait_fixed(wait_base):
"""Wait strategy that waits a fixed amount of time between each retry."""
def __init__(self, wait):
self.wait_fixed = wait
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
return self.wait_fixed
class wait_none(wait_fixed):
"""Wait strategy that doesn't wait at all before retrying."""
def __init__(self):
super(wait_none, self).__init__(0)
class wait_random(wait_base):
"""Wait strategy that waits a random amount of time between min/max."""
def __init__(self, min=0, max=1): # noqa
self.wait_random_min = min
self.wait_random_max = max
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
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):
self.wait_funcs = tuple(_compat.wait_func_accept_retry_state(strategy)
for strategy in strategies)
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
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):
self.strategies = [_compat.wait_func_accept_retry_state(strategy)
for strategy in strategies]
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
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=0, increment=100, max=_utils.MAX_WAIT): # noqa
self.start = start
self.increment = increment
self.max = max
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
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=1, max=_utils.MAX_WAIT, exp_base=2, min=0): # noqa
self.multiplier = multiplier
self.min = min
self.max = max
self.exp_base = exp_base
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
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.
"""
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
high = super(wait_random_exponential, self).__call__(
retry_state=retry_state)
return random.uniform(0, high)

View File

@ -15,8 +15,8 @@ requests==2.25.1
idna==2.10
urllib3==1.26.2
resolvelib==0.5.4
retrying==1.3.3
setuptools==44.0.0
six==1.15.0
tenacity==6.3.1
toml==0.10.2
webencodings==0.5.1