diff --git a/news/tenacity.vendor.rst b/news/tenacity.vendor.rst new file mode 100644 index 00000000000..b6938a56df0 --- /dev/null +++ b/news/tenacity.vendor.rst @@ -0,0 +1 @@ +Switch from retrying to tenacity diff --git a/pyproject.toml b/pyproject.toml index 281594b21c7..073362cebb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,8 @@ drop = [ "setuptools", "pkg_resources/_vendor/", "pkg_resources/extern/", + # unneeded part for tenacity + "tenacity/tests", ] [tool.vendoring.typing-stubs] diff --git a/src/pip/_internal/utils/filesystem.py b/src/pip/_internal/utils/filesystem.py index e9aa97685a3..14553d377c6 100644 --- a/src/pip/_internal/utils/filesystem.py +++ b/src/pip/_internal/utils/filesystem.py @@ -9,9 +9,7 @@ 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) diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index b3650084818..3e5f1753e1d 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -31,10 +31,7 @@ ) 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, diff --git a/src/pip/_vendor/__init__.py b/src/pip/_vendor/__init__.py index 0abe99d4c98..a10ecd6074a 100644 --- a/src/pip/_vendor/__init__.py +++ b/src/pip/_vendor/__init__.py @@ -75,7 +75,6 @@ def vendored(modulename): vendored("pep517") vendored("pkg_resources") vendored("progress") - vendored("retrying") vendored("requests") vendored("requests.exceptions") vendored("requests.packages") @@ -107,6 +106,7 @@ def vendored(modulename): vendored("requests.packages.urllib3.util.timeout") vendored("requests.packages.urllib3.util.url") vendored("resolvelib") + vendored("tenacity") vendored("toml") vendored("toml.encoder") vendored("toml.decoder") diff --git a/src/pip/_vendor/retrying.py b/src/pip/_vendor/retrying.py deleted file mode 100644 index 6d1e627aae8..00000000000 --- a/src/pip/_vendor/retrying.py +++ /dev/null @@ -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) diff --git a/src/pip/_vendor/retrying.pyi b/src/pip/_vendor/retrying.pyi deleted file mode 100644 index 90f20c6dbc1..00000000000 --- a/src/pip/_vendor/retrying.pyi +++ /dev/null @@ -1 +0,0 @@ -from retrying import * \ No newline at end of file diff --git a/src/pip/_vendor/tenacity.pyi b/src/pip/_vendor/tenacity.pyi new file mode 100644 index 00000000000..baf1de9dd9f --- /dev/null +++ b/src/pip/_vendor/tenacity.pyi @@ -0,0 +1 @@ +from tenacity import * \ No newline at end of file diff --git a/src/pip/_vendor/retrying.LICENSE b/src/pip/_vendor/tenacity/LICENSE similarity index 100% rename from src/pip/_vendor/retrying.LICENSE rename to src/pip/_vendor/tenacity/LICENSE diff --git a/src/pip/_vendor/tenacity/__init__.py b/src/pip/_vendor/tenacity/__init__.py new file mode 100644 index 00000000000..339ca4c7283 --- /dev/null +++ b/src/pip/_vendor/tenacity/__init__.py @@ -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 diff --git a/src/pip/_vendor/tenacity/_asyncio.py b/src/pip/_vendor/tenacity/_asyncio.py new file mode 100644 index 00000000000..51e348a3dc4 --- /dev/null +++ b/src/pip/_vendor/tenacity/_asyncio.py @@ -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 diff --git a/src/pip/_vendor/tenacity/_utils.py b/src/pip/_vendor/tenacity/_utils.py new file mode 100644 index 00000000000..365b11d4b16 --- /dev/null +++ b/src/pip/_vendor/tenacity/_utils.py @@ -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 diff --git a/src/pip/_vendor/tenacity/after.py b/src/pip/_vendor/tenacity/after.py new file mode 100644 index 00000000000..8b6082c683a --- /dev/null +++ b/src/pip/_vendor/tenacity/after.py @@ -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 diff --git a/src/pip/_vendor/tenacity/before.py b/src/pip/_vendor/tenacity/before.py new file mode 100644 index 00000000000..3eab08afb9d --- /dev/null +++ b/src/pip/_vendor/tenacity/before.py @@ -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 diff --git a/src/pip/_vendor/tenacity/before_sleep.py b/src/pip/_vendor/tenacity/before_sleep.py new file mode 100644 index 00000000000..4285922fd2d --- /dev/null +++ b/src/pip/_vendor/tenacity/before_sleep.py @@ -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 diff --git a/src/pip/_vendor/tenacity/compat.py b/src/pip/_vendor/tenacity/compat.py new file mode 100644 index 00000000000..a71ba604813 --- /dev/null +++ b/src/pip/_vendor/tenacity/compat.py @@ -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 diff --git a/src/pip/_vendor/tenacity/nap.py b/src/pip/_vendor/tenacity/nap.py new file mode 100644 index 00000000000..83ff839c361 --- /dev/null +++ b/src/pip/_vendor/tenacity/nap.py @@ -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) diff --git a/src/pip/_vendor/tenacity/py.typed b/src/pip/_vendor/tenacity/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pip/_vendor/tenacity/retry.py b/src/pip/_vendor/tenacity/retry.py new file mode 100644 index 00000000000..7340019ae05 --- /dev/null +++ b/src/pip/_vendor/tenacity/retry.py @@ -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) diff --git a/src/pip/_vendor/tenacity/stop.py b/src/pip/_vendor/tenacity/stop.py new file mode 100644 index 00000000000..a00c259b426 --- /dev/null +++ b/src/pip/_vendor/tenacity/stop.py @@ -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 diff --git a/src/pip/_vendor/tenacity/tornadoweb.py b/src/pip/_vendor/tenacity/tornadoweb.py new file mode 100644 index 00000000000..c31f7ebb76e --- /dev/null +++ b/src/pip/_vendor/tenacity/tornadoweb.py @@ -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) diff --git a/src/pip/_vendor/tenacity/wait.py b/src/pip/_vendor/tenacity/wait.py new file mode 100644 index 00000000000..8ce205e58cf --- /dev/null +++ b/src/pip/_vendor/tenacity/wait.py @@ -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) diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index 51a5508479e..0032327a291 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -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