diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-05-22 09:57:16 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-05-22 10:06:32 +0300 |
commit | 26e7194f0932c4c31244dcd564d73b40f1c624fe (patch) | |
tree | 1c455652c31db45ee878d0c1038627600510e364 | |
parent | e6cc62cba5cb059006a6107adb6a239d2be96871 (diff) | |
download | ydb-26e7194f0932c4c31244dcd564d73b40f1c624fe.tar.gz |
Intermediate changes
-rw-r--r-- | contrib/libs/opentelemetry-proto/ya.make | 4 | ||||
-rw-r--r-- | contrib/python/psutil/py3/ya.make | 2 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/.dist-info/METADATA | 11 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/README.rst | 60 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/__init__.py | 174 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/_asyncio.py | 64 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/_utils.py | 17 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/before.py | 4 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/before_sleep.py | 3 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/retry.py | 8 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/stop.py | 29 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/tornadoweb.py | 6 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/tenacity/wait.py | 12 | ||||
-rw-r--r-- | contrib/python/tenacity/py3/ya.make | 2 |
14 files changed, 292 insertions, 104 deletions
diff --git a/contrib/libs/opentelemetry-proto/ya.make b/contrib/libs/opentelemetry-proto/ya.make index 0eaa332f0c..8d341a731a 100644 --- a/contrib/libs/opentelemetry-proto/ya.make +++ b/contrib/libs/opentelemetry-proto/ya.make @@ -6,9 +6,9 @@ LICENSE(Apache-2.0) LICENSE_TEXTS(.yandex_meta/licenses.list.txt) -VERSION(1.3.0) +VERSION(1.3.1) -ORIGINAL_SOURCE(https://github.com/open-telemetry/opentelemetry-proto/archive/v1.3.0.tar.gz) +ORIGINAL_SOURCE(https://github.com/open-telemetry/opentelemetry-proto/archive/v1.3.1.tar.gz) PY_NAMESPACE(.) diff --git a/contrib/python/psutil/py3/ya.make b/contrib/python/psutil/py3/ya.make index 3c2c3464ab..2177ad09c8 100644 --- a/contrib/python/psutil/py3/ya.make +++ b/contrib/python/psutil/py3/ya.make @@ -1,3 +1,5 @@ +# Generated by devtools/yamaker (pypi). + PY3_LIBRARY() VERSION(5.9.8) diff --git a/contrib/python/tenacity/py3/.dist-info/METADATA b/contrib/python/tenacity/py3/.dist-info/METADATA index c8f74da305..3e509464e2 100644 --- a/contrib/python/tenacity/py3/.dist-info/METADATA +++ b/contrib/python/tenacity/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tenacity -Version: 8.2.3 +Version: 8.3.0 Summary: Retry code until it succeeds Home-page: https://github.com/jd/tenacity Author: Julien Danjou @@ -11,17 +11,20 @@ Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Utilities -Requires-Python: >=3.7 +Requires-Python: >=3.8 License-File: LICENSE Provides-Extra: doc Requires-Dist: reno ; extra == 'doc' Requires-Dist: sphinx ; extra == 'doc' -Requires-Dist: tornado >=4.5 ; extra == 'doc' +Provides-Extra: test +Requires-Dist: pytest ; extra == 'test' +Requires-Dist: tornado >=4.5 ; extra == 'test' +Requires-Dist: typeguard ; extra == 'test' Tenacity is a general-purpose retrying library to simplify the task of adding retry behavior to just about anything. diff --git a/contrib/python/tenacity/py3/README.rst b/contrib/python/tenacity/py3/README.rst index ce586f9e6d..bdf7ff2117 100644 --- a/contrib/python/tenacity/py3/README.rst +++ b/contrib/python/tenacity/py3/README.rst @@ -124,6 +124,16 @@ retrying stuff. print("Stopping after 10 seconds") raise Exception +If you're on a tight deadline, and exceeding your delay time isn't ok, +then you can give up on retries one attempt before you would exceed the delay. + +.. testcode:: + + @retry(stop=stop_before_delay(10)) + def stop_before_10_s(): + print("Stopping 1 attempt before 10 seconds") + raise Exception + You can combine several stop conditions by using the `|` operator: .. testcode:: @@ -402,43 +412,7 @@ without raising an exception (or you can re-raise or do anything really) RetryCallState ~~~~~~~~~~~~~~ -``retry_state`` argument is an object of `RetryCallState` class: - -.. autoclass:: tenacity.RetryCallState - - Constant attributes: - - .. autoattribute:: start_time(float) - :annotation: - - .. autoattribute:: retry_object(BaseRetrying) - :annotation: - - .. autoattribute:: fn(callable) - :annotation: - - .. autoattribute:: args(tuple) - :annotation: - - .. autoattribute:: kwargs(dict) - :annotation: - - Variable attributes: - - .. autoattribute:: attempt_number(int) - :annotation: - - .. autoattribute:: outcome(tenacity.Future or None) - :annotation: - - .. autoattribute:: outcome_timestamp(float or None) - :annotation: - - .. autoattribute:: idle_for(float) - :annotation: - - .. autoattribute:: next_action(tenacity.RetryAction or None) - :annotation: +``retry_state`` argument is an object of :class:`~tenacity.RetryCallState` class. Other Custom Callbacks ~~~~~~~~~~~~~~~~~~~~~~ @@ -447,33 +421,33 @@ It's also possible to define custom callbacks for other keyword arguments. .. function:: my_stop(retry_state) - :param RetryState retry_state: info about current retry invocation + :param RetryCallState retry_state: info about current retry invocation :return: whether or not retrying should stop :rtype: bool .. function:: my_wait(retry_state) - :param RetryState retry_state: info about current retry invocation + :param RetryCallState retry_state: info about current retry invocation :return: number of seconds to wait before next retry :rtype: float .. function:: my_retry(retry_state) - :param RetryState retry_state: info about current retry invocation + :param RetryCallState retry_state: info about current retry invocation :return: whether or not retrying should continue :rtype: bool .. function:: my_before(retry_state) - :param RetryState retry_state: info about current retry invocation + :param RetryCallState retry_state: info about current retry invocation .. function:: my_after(retry_state) - :param RetryState retry_state: info about current retry invocation + :param RetryCallState retry_state: info about current retry invocation .. function:: my_before_sleep(retry_state) - :param RetryState retry_state: info about current retry invocation + :param RetryCallState retry_state: info about current retry invocation Here's an example with a custom ``before_sleep`` function: diff --git a/contrib/python/tenacity/py3/tenacity/__init__.py b/contrib/python/tenacity/py3/tenacity/__init__.py index ba8011be02..bcee3f5910 100644 --- a/contrib/python/tenacity/py3/tenacity/__init__.py +++ b/contrib/python/tenacity/py3/tenacity/__init__.py @@ -15,8 +15,7 @@ # 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 dataclasses import functools import sys import threading @@ -50,6 +49,7 @@ 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_before_delay # noqa from .stop import stop_all # noqa from .stop import stop_any # noqa from .stop import stop_never # noqa @@ -96,6 +96,29 @@ WrappedFnReturnT = t.TypeVar("WrappedFnReturnT") WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any]) +dataclass_kwargs = {} +if sys.version_info >= (3, 10): + dataclass_kwargs.update({"slots": True}) + + +@dataclasses.dataclass(**dataclass_kwargs) +class IterState: + actions: t.List[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field( + default_factory=list + ) + retry_run_result: bool = False + delay_since_first_attempt: int = 0 + stop_run_result: bool = False + is_explicit_retry: bool = False + + def reset(self) -> None: + self.actions = [] + self.retry_run_result = False + self.delay_since_first_attempt = 0 + self.stop_run_result = False + self.is_explicit_retry = False + + class TryAgain(Exception): """Always retry the executed function when raised.""" @@ -124,7 +147,9 @@ class BaseAction: NAME: t.Optional[str] = None def __repr__(self) -> str: - state_str = ", ".join(f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS) + state_str = ", ".join( + f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS + ) return f"{self.__class__.__name__}({state_str})" def __str__(self) -> str: @@ -220,10 +245,14 @@ class BaseRetrying(ABC): retry: t.Union[retry_base, object] = _unset, before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, - before_sleep: t.Union[t.Optional[t.Callable[["RetryCallState"], None]], object] = _unset, + before_sleep: t.Union[ + t.Optional[t.Callable[["RetryCallState"], None]], object + ] = _unset, reraise: t.Union[bool, object] = _unset, retry_error_cls: t.Union[t.Type[RetryError], object] = _unset, - retry_error_callback: t.Union[t.Optional[t.Callable[["RetryCallState"], t.Any]], object] = _unset, + retry_error_callback: t.Union[ + t.Optional[t.Callable[["RetryCallState"], t.Any]], object + ] = _unset, ) -> "BaseRetrying": """Copy this object with some parameters changed if needed.""" return self.__class__( @@ -236,7 +265,9 @@ class BaseRetrying(ABC): before_sleep=_first_set(before_sleep, self.before_sleep), reraise=_first_set(reraise, self.reraise), retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls), - retry_error_callback=_first_set(retry_error_callback, self.retry_error_callback), + retry_error_callback=_first_set( + retry_error_callback, self.retry_error_callback + ), ) def __repr__(self) -> str: @@ -278,13 +309,23 @@ class BaseRetrying(ABC): self._local.statistics = t.cast(t.Dict[str, t.Any], {}) return self._local.statistics + @property + def iter_state(self) -> IterState: + try: + return self._local.iter_state # type: ignore[no-any-return] + except AttributeError: + self._local.iter_state = IterState() + return self._local.iter_state + def wraps(self, f: WrappedFn) -> WrappedFn: """Wrap a function for retrying. :param f: A function to wraps for retrying. """ - @functools.wraps(f) + @functools.wraps( + f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__") + ) def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any: return self(f, *args, **kw) @@ -302,42 +343,89 @@ class BaseRetrying(ABC): self.statistics["attempt_number"] = 1 self.statistics["idle_for"] = 0 + def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None: + self.iter_state.actions.append(fn) + + def _run_retry(self, retry_state: "RetryCallState") -> None: + self.iter_state.retry_run_result = self.retry(retry_state) + + def _run_wait(self, retry_state: "RetryCallState") -> None: + if self.wait: + sleep = self.wait(retry_state) + else: + sleep = 0.0 + + retry_state.upcoming_sleep = sleep + + def _run_stop(self, retry_state: "RetryCallState") -> None: + self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start + self.iter_state.stop_run_result = self.stop(retry_state) + def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa + self._begin_iter(retry_state) + result = None + for action in self.iter_state.actions: + result = action(retry_state) + return result + + def _begin_iter(self, retry_state: "RetryCallState") -> None: # noqa + self.iter_state.reset() + fut = retry_state.outcome if fut is None: if self.before is not None: - self.before(retry_state) - return DoAttempt() + self._add_action_func(self.before) + self._add_action_func(lambda rs: DoAttempt()) + return + + self.iter_state.is_explicit_retry = fut.failed and isinstance( + fut.exception(), TryAgain + ) + if not self.iter_state.is_explicit_retry: + self._add_action_func(self._run_retry) + self._add_action_func(self._post_retry_check_actions) - is_explicit_retry = fut.failed and isinstance(fut.exception(), TryAgain) - if not (is_explicit_retry or self.retry(retry_state)): - return fut.result() + def _post_retry_check_actions(self, retry_state: "RetryCallState") -> None: + if not (self.iter_state.is_explicit_retry or self.iter_state.retry_run_result): + self._add_action_func(lambda rs: rs.outcome.result()) + return if self.after is not None: - self.after(retry_state) + self._add_action_func(self.after) - self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start - if self.stop(retry_state): + self._add_action_func(self._run_wait) + self._add_action_func(self._run_stop) + self._add_action_func(self._post_stop_check_actions) + + def _post_stop_check_actions(self, retry_state: "RetryCallState") -> None: + if self.iter_state.stop_run_result: if self.retry_error_callback: - return self.retry_error_callback(retry_state) - retry_exc = self.retry_error_cls(fut) - if self.reraise: - raise retry_exc.reraise() - raise retry_exc from fut.exception() + self._add_action_func(self.retry_error_callback) + return - if self.wait: - sleep = self.wait(retry_state) - else: - sleep = 0.0 - retry_state.next_action = RetryAction(sleep) - retry_state.idle_for += sleep - self.statistics["idle_for"] += sleep - self.statistics["attempt_number"] += 1 + def exc_check(rs: "RetryCallState") -> None: + fut = t.cast(Future, rs.outcome) + retry_exc = self.retry_error_cls(fut) + if self.reraise: + raise retry_exc.reraise() + raise retry_exc from fut.exception() + + self._add_action_func(exc_check) + return + + def next_action(rs: "RetryCallState") -> None: + sleep = rs.upcoming_sleep + rs.next_action = RetryAction(sleep) + rs.idle_for += sleep + self.statistics["idle_for"] += sleep + self.statistics["attempt_number"] += 1 + + self._add_action_func(next_action) if self.before_sleep is not None: - self.before_sleep(retry_state) + self._add_action_func(self.before_sleep) - return DoSleep(sleep) + self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep)) def __iter__(self) -> t.Generator[AttemptManager, None, None]: self.begin() @@ -391,7 +479,7 @@ class Retrying(BaseRetrying): return do # type: ignore[no-any-return] -if sys.version_info[1] >= 9: +if sys.version_info >= (3, 9): FutureGenericT = futures.Future[t.Any] else: FutureGenericT = futures.Future @@ -410,7 +498,9 @@ class Future(FutureGenericT): return self.exception() is not None @classmethod - def construct(cls, attempt_number: int, value: t.Any, has_exception: bool) -> "Future": + def construct( + cls, attempt_number: int, value: t.Any, has_exception: bool + ) -> "Future": """Construct a new Future object.""" fut = cls(attempt_number) if has_exception: @@ -451,6 +541,8 @@ class RetryCallState: self.idle_for: float = 0.0 #: Next action as decided by the retry manager self.next_action: t.Optional[RetryAction] = None + #: Next sleep time as decided by the retry manager. + self.upcoming_sleep: float = 0.0 @property def seconds_since_start(self) -> t.Optional[float]: @@ -471,7 +563,10 @@ class RetryCallState: self.outcome, self.outcome_timestamp = fut, ts def set_exception( - self, exc_info: t.Tuple[t.Type[BaseException], BaseException, "types.TracebackType| None"] + self, + exc_info: t.Tuple[ + t.Type[BaseException], BaseException, "types.TracebackType| None" + ], ) -> None: ts = time.monotonic() fut = Future(self.attempt_number) @@ -493,8 +588,7 @@ class RetryCallState: @t.overload -def retry(func: WrappedFn) -> WrappedFn: - ... +def retry(func: WrappedFn) -> WrappedFn: ... @t.overload @@ -509,8 +603,7 @@ def retry( reraise: bool = False, retry_error_cls: t.Type["RetryError"] = RetryError, retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None, -) -> t.Callable[[WrappedFn], WrappedFn]: - ... +) -> t.Callable[[WrappedFn], WrappedFn]: ... def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any: @@ -533,7 +626,11 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any: r: "BaseRetrying" if iscoroutinefunction(f): r = AsyncRetrying(*dargs, **dkw) - elif tornado and hasattr(tornado.gen, "is_coroutine_function") and tornado.gen.is_coroutine_function(f): + 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) @@ -568,6 +665,7 @@ __all__ = [ "sleep_using_event", "stop_after_attempt", "stop_after_delay", + "stop_before_delay", "stop_all", "stop_any", "stop_never", diff --git a/contrib/python/tenacity/py3/tenacity/_asyncio.py b/contrib/python/tenacity/py3/tenacity/_asyncio.py index 9e10c072eb..b06303f484 100644 --- a/contrib/python/tenacity/py3/tenacity/_asyncio.py +++ b/contrib/python/tenacity/py3/tenacity/_asyncio.py @@ -18,22 +18,33 @@ import functools import sys import typing as t -from asyncio import sleep from tenacity import AttemptManager from tenacity import BaseRetrying from tenacity import DoAttempt from tenacity import DoSleep from tenacity import RetryCallState +from tenacity import _utils WrappedFnReturnT = t.TypeVar("WrappedFnReturnT") WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]]) +def asyncio_sleep(duration: float) -> t.Awaitable[None]: + # Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead). + import asyncio + + return asyncio.sleep(duration) + + class AsyncRetrying(BaseRetrying): sleep: t.Callable[[float], t.Awaitable[t.Any]] - def __init__(self, sleep: t.Callable[[float], t.Awaitable[t.Any]] = sleep, **kwargs: t.Any) -> None: + def __init__( + self, + sleep: t.Callable[[float], t.Awaitable[t.Any]] = asyncio_sleep, + **kwargs: t.Any, + ) -> None: super().__init__(**kwargs) self.sleep = sleep @@ -44,7 +55,7 @@ class AsyncRetrying(BaseRetrying): retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) while True: - do = self.iter(retry_state=retry_state) + do = await self.iter(retry_state=retry_state) if isinstance(do, DoAttempt): try: result = await fn(*args, **kwargs) @@ -58,6 +69,47 @@ class AsyncRetrying(BaseRetrying): else: return do # type: ignore[no-any-return] + @classmethod + def _wrap_action_func(cls, fn: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + if _utils.is_coroutine_callable(fn): + return fn + + async def inner(*args: t.Any, **kwargs: t.Any) -> t.Any: + return fn(*args, **kwargs) + + return inner + + def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None: + self.iter_state.actions.append(self._wrap_action_func(fn)) + + async def _run_retry(self, retry_state: "RetryCallState") -> None: # type: ignore[override] + self.iter_state.retry_run_result = await self._wrap_action_func(self.retry)( + retry_state + ) + + async def _run_wait(self, retry_state: "RetryCallState") -> None: # type: ignore[override] + if self.wait: + sleep = await self._wrap_action_func(self.wait)(retry_state) + else: + sleep = 0.0 + + retry_state.upcoming_sleep = sleep + + async def _run_stop(self, retry_state: "RetryCallState") -> None: # type: ignore[override] + self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start + self.iter_state.stop_run_result = await self._wrap_action_func(self.stop)( + retry_state + ) + + async def iter( + self, retry_state: "RetryCallState" + ) -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa: A003 + self._begin_iter(retry_state) + result = None + for action in self.iter_state.actions: + result = await action(retry_state) + return result + def __iter__(self) -> t.Generator[AttemptManager, None, None]: raise TypeError("AsyncRetrying object is not iterable") @@ -68,7 +120,7 @@ class AsyncRetrying(BaseRetrying): async def __anext__(self) -> AttemptManager: while True: - do = self.iter(retry_state=self._retry_state) + do = await self.iter(retry_state=self._retry_state) if do is None: raise StopAsyncIteration elif isinstance(do, DoAttempt): @@ -83,7 +135,9 @@ class AsyncRetrying(BaseRetrying): fn = super().wraps(fn) # Ensure wrapper is recognized as a coroutine function. - @functools.wraps(fn) + @functools.wraps( + fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__") + ) async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any: return await fn(*args, **kwargs) diff --git a/contrib/python/tenacity/py3/tenacity/_utils.py b/contrib/python/tenacity/py3/tenacity/_utils.py index f14ff32096..4e34115e0e 100644 --- a/contrib/python/tenacity/py3/tenacity/_utils.py +++ b/contrib/python/tenacity/py3/tenacity/_utils.py @@ -13,7 +13,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import functools +import inspect import sys import typing from datetime import timedelta @@ -73,4 +74,16 @@ time_unit_type = typing.Union[int, float, timedelta] def to_seconds(time_unit: time_unit_type) -> float: - return float(time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit) + return float( + time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit + ) + + +def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool: + if inspect.isclass(call): + return False + if inspect.iscoroutinefunction(call): + return True + partial_call = isinstance(call, functools.partial) and call.func + dunder_call = partial_call or getattr(call, "__call__", None) + return inspect.iscoroutinefunction(dunder_call) diff --git a/contrib/python/tenacity/py3/tenacity/before.py b/contrib/python/tenacity/py3/tenacity/before.py index 9284f7ae5b..366235af6a 100644 --- a/contrib/python/tenacity/py3/tenacity/before.py +++ b/contrib/python/tenacity/py3/tenacity/before.py @@ -28,7 +28,9 @@ def before_nothing(retry_state: "RetryCallState") -> None: """Before call strategy that does nothing.""" -def before_log(logger: "logging.Logger", log_level: int) -> typing.Callable[["RetryCallState"], None]: +def before_log( + logger: "logging.Logger", log_level: int +) -> typing.Callable[["RetryCallState"], None]: """Before call strategy that logs to some logger the attempt.""" def log_it(retry_state: "RetryCallState") -> None: diff --git a/contrib/python/tenacity/py3/tenacity/before_sleep.py b/contrib/python/tenacity/py3/tenacity/before_sleep.py index 279a21eb5b..d04edcf9bd 100644 --- a/contrib/python/tenacity/py3/tenacity/before_sleep.py +++ b/contrib/python/tenacity/py3/tenacity/before_sleep.py @@ -64,7 +64,8 @@ def before_sleep_log( logger.log( log_level, - f"Retrying {fn_name} " f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.", + f"Retrying {fn_name} " + f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.", exc_info=local_exc_info, ) diff --git a/contrib/python/tenacity/py3/tenacity/retry.py b/contrib/python/tenacity/py3/tenacity/retry.py index 765b6fe14a..c5e55a653f 100644 --- a/contrib/python/tenacity/py3/tenacity/retry.py +++ b/contrib/python/tenacity/py3/tenacity/retry.py @@ -204,7 +204,9 @@ class retry_if_exception_message(retry_if_exception): match: typing.Optional[str] = None, ) -> None: if message and match: - raise TypeError(f"{self.__class__.__name__}() takes either 'message' or 'match', not both") + raise TypeError( + f"{self.__class__.__name__}() takes either 'message' or 'match', not both" + ) # set predicate if message: @@ -221,7 +223,9 @@ class retry_if_exception_message(retry_if_exception): predicate = match_fnc else: - raise TypeError(f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'") + raise TypeError( + f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'" + ) super().__init__(predicate) diff --git a/contrib/python/tenacity/py3/tenacity/stop.py b/contrib/python/tenacity/py3/tenacity/stop.py index e64786063f..5cda59ab21 100644 --- a/contrib/python/tenacity/py3/tenacity/stop.py +++ b/contrib/python/tenacity/py3/tenacity/stop.py @@ -92,7 +92,14 @@ class stop_after_attempt(stop_base): class stop_after_delay(stop_base): - """Stop when the time from the first attempt >= limit.""" + """ + Stop when the time from the first attempt >= limit. + + Note: `max_delay` will be exceeded, so when used with a `wait`, the actual total delay will be greater + than `max_delay` by some of the final sleep period before `max_delay` is exceeded. + + If you need stricter timing with waits, consider `stop_before_delay` instead. + """ def __init__(self, max_delay: _utils.time_unit_type) -> None: self.max_delay = _utils.to_seconds(max_delay) @@ -101,3 +108,23 @@ class stop_after_delay(stop_base): if retry_state.seconds_since_start is None: raise RuntimeError("__call__() called but seconds_since_start is not set") return retry_state.seconds_since_start >= self.max_delay + + +class stop_before_delay(stop_base): + """ + Stop right before the next attempt would take place after the time from the first attempt >= limit. + + Most useful when you are using with a `wait` function like wait_random_exponential, but need to make + sure that the max_delay is not exceeded. + """ + + def __init__(self, max_delay: _utils.time_unit_type) -> None: + self.max_delay = _utils.to_seconds(max_delay) + + def __call__(self, retry_state: "RetryCallState") -> bool: + if retry_state.seconds_since_start is None: + raise RuntimeError("__call__() called but seconds_since_start is not set") + return ( + retry_state.seconds_since_start + retry_state.upcoming_sleep + >= self.max_delay + ) diff --git a/contrib/python/tenacity/py3/tenacity/tornadoweb.py b/contrib/python/tenacity/py3/tenacity/tornadoweb.py index fabf13ae2e..44323e40da 100644 --- a/contrib/python/tenacity/py3/tenacity/tornadoweb.py +++ b/contrib/python/tenacity/py3/tenacity/tornadoweb.py @@ -29,7 +29,11 @@ _RetValT = typing.TypeVar("_RetValT") class TornadoRetrying(BaseRetrying): - def __init__(self, sleep: "typing.Callable[[float], Future[None]]" = gen.sleep, **kwargs: typing.Any) -> None: + def __init__( + self, + sleep: "typing.Callable[[float], Future[None]]" = gen.sleep, + **kwargs: typing.Any, + ) -> None: super().__init__(**kwargs) self.sleep = sleep diff --git a/contrib/python/tenacity/py3/tenacity/wait.py b/contrib/python/tenacity/py3/tenacity/wait.py index e1e2fe48bc..3addbb9c4a 100644 --- a/contrib/python/tenacity/py3/tenacity/wait.py +++ b/contrib/python/tenacity/py3/tenacity/wait.py @@ -41,7 +41,9 @@ class wait_base(abc.ABC): return self.__add__(other) -WaitBaseT = typing.Union[wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]] +WaitBaseT = typing.Union[ + wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]] +] class wait_fixed(wait_base): @@ -64,12 +66,16 @@ class wait_none(wait_fixed): class wait_random(wait_base): """Wait strategy that waits a random amount of time between min/max.""" - def __init__(self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1) -> None: # noqa + def __init__( + self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1 + ) -> None: # noqa self.wait_random_min = _utils.to_seconds(min) self.wait_random_max = _utils.to_seconds(max) def __call__(self, retry_state: "RetryCallState") -> float: - return self.wait_random_min + (random.random() * (self.wait_random_max - self.wait_random_min)) + return self.wait_random_min + ( + random.random() * (self.wait_random_max - self.wait_random_min) + ) class wait_combine(wait_base): diff --git a/contrib/python/tenacity/py3/ya.make b/contrib/python/tenacity/py3/ya.make index 9b5488ebed..1670ca9908 100644 --- a/contrib/python/tenacity/py3/ya.make +++ b/contrib/python/tenacity/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(8.2.3) +VERSION(8.3.0) LICENSE(Apache-2.0) |