aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2024-05-22 09:57:16 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2024-05-22 10:06:32 +0300
commit26e7194f0932c4c31244dcd564d73b40f1c624fe (patch)
tree1c455652c31db45ee878d0c1038627600510e364
parente6cc62cba5cb059006a6107adb6a239d2be96871 (diff)
downloadydb-26e7194f0932c4c31244dcd564d73b40f1c624fe.tar.gz
Intermediate changes
-rw-r--r--contrib/libs/opentelemetry-proto/ya.make4
-rw-r--r--contrib/python/psutil/py3/ya.make2
-rw-r--r--contrib/python/tenacity/py3/.dist-info/METADATA11
-rw-r--r--contrib/python/tenacity/py3/README.rst60
-rw-r--r--contrib/python/tenacity/py3/tenacity/__init__.py174
-rw-r--r--contrib/python/tenacity/py3/tenacity/_asyncio.py64
-rw-r--r--contrib/python/tenacity/py3/tenacity/_utils.py17
-rw-r--r--contrib/python/tenacity/py3/tenacity/before.py4
-rw-r--r--contrib/python/tenacity/py3/tenacity/before_sleep.py3
-rw-r--r--contrib/python/tenacity/py3/tenacity/retry.py8
-rw-r--r--contrib/python/tenacity/py3/tenacity/stop.py29
-rw-r--r--contrib/python/tenacity/py3/tenacity/tornadoweb.py6
-rw-r--r--contrib/python/tenacity/py3/tenacity/wait.py12
-rw-r--r--contrib/python/tenacity/py3/ya.make2
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)