Browse Source

Intermediate changes

robot-piglet 8 months ago
parent
commit
5f2323ef7b

+ 1 - 1
contrib/python/tenacity/py3/.dist-info/METADATA

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: tenacity
-Version: 8.3.0
+Version: 8.4.1
 Summary: Retry code until it succeeds
 Home-page: https://github.com/jd/tenacity
 Author: Julien Danjou

+ 13 - 7
contrib/python/tenacity/py3/README.rst

@@ -79,7 +79,7 @@ Examples
 Basic Retry
 ~~~~~~~~~~~
 
-.. testsetup:: *
+.. testsetup::
 
     import logging
     #
@@ -568,28 +568,34 @@ in retry strategies like ``retry_if_result``. This can be done accessing the
 Async and retry
 ~~~~~~~~~~~~~~~
 
-Finally, ``retry`` works also on asyncio and Tornado (>= 4.5) coroutines.
+Finally, ``retry`` works also on asyncio, Trio, and Tornado (>= 4.5) coroutines.
 Sleeps are done asynchronously too.
 
 .. code-block:: python
 
     @retry
-    async def my_async_function(loop):
+    async def my_asyncio_function(loop):
         await loop.getaddrinfo('8.8.8.8', 53)
 
+.. code-block:: python
+
+    @retry
+    async def my_async_trio_function():
+        await trio.socket.getaddrinfo('8.8.8.8', 53)
+
 .. code-block:: python
 
     @retry
     @tornado.gen.coroutine
-    def my_async_function(http_client, url):
+    def my_async_tornado_function(http_client, url):
         yield http_client.fetch(url)
 
-You can even use alternative event loops such as `curio` or `Trio` by passing the correct sleep function:
+You can even use alternative event loops such as `curio` by passing the correct sleep function:
 
 .. code-block:: python
 
-    @retry(sleep=trio.sleep)
-    async def my_async_function(loop):
+    @retry(sleep=curio.sleep)
+    async def my_async_curio_function():
         await asks.get('https://example.org')
 
 Contribute

+ 19 - 9
contrib/python/tenacity/py3/tenacity/__init__.py

@@ -24,7 +24,8 @@ import typing as t
 import warnings
 from abc import ABC, abstractmethod
 from concurrent import futures
-from inspect import iscoroutinefunction
+
+from . import _utils
 
 # Import all built-in retry strategies for easier usage.
 from .retry import retry_base  # noqa
@@ -87,6 +88,7 @@ except ImportError:
 if t.TYPE_CHECKING:
     import types
 
+    from . import asyncio as tasyncio
     from .retry import RetryBaseT
     from .stop import StopBaseT
     from .wait import WaitBaseT
@@ -593,16 +595,24 @@ def retry(func: WrappedFn) -> WrappedFn: ...
 
 @t.overload
 def retry(
-    sleep: t.Callable[[t.Union[int, float]], t.Optional[t.Awaitable[None]]] = sleep,
+    sleep: t.Callable[[t.Union[int, float]], t.Union[None, t.Awaitable[None]]] = sleep,
     stop: "StopBaseT" = stop_never,
     wait: "WaitBaseT" = wait_none(),
-    retry: "RetryBaseT" = retry_if_exception_type(),
-    before: t.Callable[["RetryCallState"], None] = before_nothing,
-    after: t.Callable[["RetryCallState"], None] = after_nothing,
-    before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None,
+    retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = retry_if_exception_type(),
+    before: t.Callable[
+        ["RetryCallState"], t.Union[None, t.Awaitable[None]]
+    ] = before_nothing,
+    after: t.Callable[
+        ["RetryCallState"], t.Union[None, t.Awaitable[None]]
+    ] = after_nothing,
+    before_sleep: t.Optional[
+        t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
+    ] = None,
     reraise: bool = False,
     retry_error_cls: t.Type["RetryError"] = RetryError,
-    retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None,
+    retry_error_callback: t.Optional[
+        t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
+    ] = None,
 ) -> t.Callable[[WrappedFn], WrappedFn]: ...
 
 
@@ -624,7 +634,7 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
                     f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)"
                 )
             r: "BaseRetrying"
-            if iscoroutinefunction(f):
+            if _utils.is_coroutine_callable(f):
                 r = AsyncRetrying(*dargs, **dkw)
             elif (
                 tornado
@@ -640,7 +650,7 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
         return wrap
 
 
-from tenacity._asyncio import AsyncRetrying  # noqa:E402,I100
+from tenacity.asyncio import AsyncRetrying  # noqa:E402,I100
 
 if tornado:
     from tenacity.tornadoweb import TornadoRetrying

+ 12 - 0
contrib/python/tenacity/py3/tenacity/_utils.py

@@ -87,3 +87,15 @@ def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:
     partial_call = isinstance(call, functools.partial) and call.func
     dunder_call = partial_call or getattr(call, "__call__", None)
     return inspect.iscoroutinefunction(dunder_call)
+
+
+def wrap_to_async_func(
+    call: typing.Callable[..., typing.Any],
+) -> typing.Callable[..., typing.Awaitable[typing.Any]]:
+    if is_coroutine_callable(call):
+        return call
+
+    async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
+        return call(*args, **kwargs)
+
+    return inner

+ 77 - 24
contrib/python/tenacity/py3/tenacity/_asyncio.py → contrib/python/tenacity/py3/tenacity/asyncio/__init__.py

@@ -19,34 +19,87 @@ import functools
 import sys
 import typing as t
 
+import tenacity
 from tenacity import AttemptManager
 from tenacity import BaseRetrying
 from tenacity import DoAttempt
 from tenacity import DoSleep
 from tenacity import RetryCallState
+from tenacity import RetryError
+from tenacity import after_nothing
+from tenacity import before_nothing
 from tenacity import _utils
 
+# Import all built-in retry strategies for easier usage.
+from .retry import RetryBaseT
+from .retry import retry_all  # noqa
+from .retry import retry_any  # noqa
+from .retry import retry_if_exception  # noqa
+from .retry import retry_if_result  # noqa
+from ..retry import RetryBaseT as SyncRetryBaseT
+
+if t.TYPE_CHECKING:
+    from tenacity.stop import StopBaseT
+    from tenacity.wait import WaitBaseT
+
 WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
 WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
 
 
-def asyncio_sleep(duration: float) -> t.Awaitable[None]:
+def _portable_async_sleep(seconds: float) -> t.Awaitable[None]:
+    # If trio is already imported, then importing it is cheap.
+    # If trio isn't already imported, then it's definitely not running, so we
+    # can skip further checks.
+    if "trio" in sys.modules:
+        # If trio is available, then sniffio is too
+        import trio
+        import sniffio
+
+        if sniffio.current_async_library() == "trio":
+            return trio.sleep(seconds)
+    # Otherwise, assume asyncio
     # Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).
     import asyncio
 
-    return asyncio.sleep(duration)
+    return asyncio.sleep(seconds)
 
 
 class AsyncRetrying(BaseRetrying):
-    sleep: t.Callable[[float], t.Awaitable[t.Any]]
-
     def __init__(
         self,
-        sleep: t.Callable[[float], t.Awaitable[t.Any]] = asyncio_sleep,
-        **kwargs: t.Any,
+        sleep: t.Callable[
+            [t.Union[int, float]], t.Union[None, t.Awaitable[None]]
+        ] = _portable_async_sleep,
+        stop: "StopBaseT" = tenacity.stop.stop_never,
+        wait: "WaitBaseT" = tenacity.wait.wait_none(),
+        retry: "t.Union[SyncRetryBaseT, RetryBaseT]" = tenacity.retry_if_exception_type(),
+        before: t.Callable[
+            ["RetryCallState"], t.Union[None, t.Awaitable[None]]
+        ] = before_nothing,
+        after: t.Callable[
+            ["RetryCallState"], t.Union[None, t.Awaitable[None]]
+        ] = after_nothing,
+        before_sleep: t.Optional[
+            t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
+        ] = None,
+        reraise: bool = False,
+        retry_error_cls: t.Type["RetryError"] = RetryError,
+        retry_error_callback: t.Optional[
+            t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
+        ] = None,
     ) -> None:
-        super().__init__(**kwargs)
-        self.sleep = sleep
+        super().__init__(
+            sleep=sleep,  # type: ignore[arg-type]
+            stop=stop,
+            wait=wait,
+            retry=retry,  # type: ignore[arg-type]
+            before=before,  # type: ignore[arg-type]
+            after=after,  # type: ignore[arg-type]
+            before_sleep=before_sleep,  # type: ignore[arg-type]
+            reraise=reraise,
+            retry_error_cls=retry_error_cls,
+            retry_error_callback=retry_error_callback,
+        )
 
     async def __call__(  # type: ignore[override]
         self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any
@@ -65,31 +118,21 @@ class AsyncRetrying(BaseRetrying):
                     retry_state.set_result(result)
             elif isinstance(do, DoSleep):
                 retry_state.prepare_for_next_attempt()
-                await self.sleep(do)
+                await self.sleep(do)  # type: ignore[misc]
             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))
+        self.iter_state.actions.append(_utils.wrap_to_async_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)(
+        self.iter_state.retry_run_result = await _utils.wrap_to_async_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)
+            sleep = await _utils.wrap_to_async_func(self.wait)(retry_state)
         else:
             sleep = 0.0
 
@@ -97,7 +140,7 @@ class AsyncRetrying(BaseRetrying):
 
     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)(
+        self.iter_state.stop_run_result = await _utils.wrap_to_async_func(self.stop)(
             retry_state
         )
 
@@ -127,7 +170,7 @@ class AsyncRetrying(BaseRetrying):
                 return AttemptManager(retry_state=self._retry_state)
             elif isinstance(do, DoSleep):
                 self._retry_state.prepare_for_next_attempt()
-                await self.sleep(do)
+                await self.sleep(do)  # type: ignore[misc]
             else:
                 raise StopAsyncIteration
 
@@ -146,3 +189,13 @@ class AsyncRetrying(BaseRetrying):
         async_wrapped.retry_with = fn.retry_with  # type: ignore[attr-defined]
 
         return async_wrapped  # type: ignore[return-value]
+
+
+__all__ = [
+    "retry_all",
+    "retry_any",
+    "retry_if_exception",
+    "retry_if_result",
+    "WrappedFn",
+    "AsyncRetrying",
+]

+ 125 - 0
contrib/python/tenacity/py3/tenacity/asyncio/retry.py

@@ -0,0 +1,125 @@
+# Copyright 2016–2021 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 typing
+
+from tenacity import _utils
+from tenacity import retry_base
+
+if typing.TYPE_CHECKING:
+    from tenacity import RetryCallState
+
+
+class async_retry_base(retry_base):
+    """Abstract base class for async retry strategies."""
+
+    @abc.abstractmethod
+    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
+        pass
+
+    def __and__(  # type: ignore[override]
+        self, other: "typing.Union[retry_base, async_retry_base]"
+    ) -> "retry_all":
+        return retry_all(self, other)
+
+    def __rand__(  # type: ignore[misc,override]
+        self, other: "typing.Union[retry_base, async_retry_base]"
+    ) -> "retry_all":
+        return retry_all(other, self)
+
+    def __or__(  # type: ignore[override]
+        self, other: "typing.Union[retry_base, async_retry_base]"
+    ) -> "retry_any":
+        return retry_any(self, other)
+
+    def __ror__(  # type: ignore[misc,override]
+        self, other: "typing.Union[retry_base, async_retry_base]"
+    ) -> "retry_any":
+        return retry_any(other, self)
+
+
+RetryBaseT = typing.Union[
+    async_retry_base, typing.Callable[["RetryCallState"], typing.Awaitable[bool]]
+]
+
+
+class retry_if_exception(async_retry_base):
+    """Retry strategy that retries if an exception verifies a predicate."""
+
+    def __init__(
+        self, predicate: typing.Callable[[BaseException], typing.Awaitable[bool]]
+    ) -> None:
+        self.predicate = predicate
+
+    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
+        if retry_state.outcome is None:
+            raise RuntimeError("__call__() called before outcome was set")
+
+        if retry_state.outcome.failed:
+            exception = retry_state.outcome.exception()
+            if exception is None:
+                raise RuntimeError("outcome failed but the exception is None")
+            return await self.predicate(exception)
+        else:
+            return False
+
+
+class retry_if_result(async_retry_base):
+    """Retries if the result verifies a predicate."""
+
+    def __init__(
+        self, predicate: typing.Callable[[typing.Any], typing.Awaitable[bool]]
+    ) -> None:
+        self.predicate = predicate
+
+    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
+        if retry_state.outcome is None:
+            raise RuntimeError("__call__() called before outcome was set")
+
+        if not retry_state.outcome.failed:
+            return await self.predicate(retry_state.outcome.result())
+        else:
+            return False
+
+
+class retry_any(async_retry_base):
+    """Retries if any of the retries condition is valid."""
+
+    def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None:
+        self.retries = retries
+
+    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
+        result = False
+        for r in self.retries:
+            result = result or await _utils.wrap_to_async_func(r)(retry_state)
+            if result:
+                break
+        return result
+
+
+class retry_all(async_retry_base):
+    """Retries if all the retries condition are valid."""
+
+    def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None:
+        self.retries = retries
+
+    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
+        result = True
+        for r in self.retries:
+            result = result and await _utils.wrap_to_async_func(r)(retry_state)
+            if not result:
+                break
+        return result

+ 8 - 2
contrib/python/tenacity/py3/tenacity/retry.py

@@ -30,10 +30,16 @@ class retry_base(abc.ABC):
         pass
 
     def __and__(self, other: "retry_base") -> "retry_all":
-        return retry_all(self, other)
+        return other.__rand__(self)
+
+    def __rand__(self, other: "retry_base") -> "retry_all":
+        return retry_all(other, self)
 
     def __or__(self, other: "retry_base") -> "retry_any":
-        return retry_any(self, other)
+        return other.__ror__(self)
+
+    def __ror__(self, other: "retry_base") -> "retry_any":
+        return retry_any(other, self)
 
 
 RetryBaseT = typing.Union[retry_base, typing.Callable[["RetryCallState"], bool]]

+ 3 - 2
contrib/python/tenacity/py3/ya.make

@@ -2,7 +2,7 @@
 
 PY3_LIBRARY()
 
-VERSION(8.3.0)
+VERSION(8.4.1)
 
 LICENSE(Apache-2.0)
 
@@ -15,9 +15,10 @@ NO_CHECK_IMPORTS(
 PY_SRCS(
     TOP_LEVEL
     tenacity/__init__.py
-    tenacity/_asyncio.py
     tenacity/_utils.py
     tenacity/after.py
+    tenacity/asyncio/__init__.py
+    tenacity/asyncio/retry.py
     tenacity/before.py
     tenacity/before_sleep.py
     tenacity/nap.py