mirror of https://github.com/pypa/pip
switch from 'retrying' to 'tenacity'
This commit is contained in:
parent
8a949a1c52
commit
64ecfc8476
|
@ -0,0 +1 @@
|
|||
Switch from retrying to tenacity
|
|
@ -44,6 +44,8 @@ drop = [
|
|||
"setuptools",
|
||||
"pkg_resources/_vendor/",
|
||||
"pkg_resources/extern/",
|
||||
# unneeded part for tenacity
|
||||
"tenacity/tests",
|
||||
]
|
||||
|
||||
[tool.vendoring.typing-stubs]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||
from retrying import *
|
|
@ -0,0 +1 @@
|
|||
from tenacity import *
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue