tenacity 8.3.0__tar.gz → 8.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tenacity-8.3.0 → tenacity-8.4.1}/.github/workflows/ci.yaml +3 -3
- {tenacity-8.3.0 → tenacity-8.4.1}/.github/workflows/deploy.yaml +2 -2
- {tenacity-8.3.0 → tenacity-8.4.1}/.mergify.yml +1 -1
- {tenacity-8.3.0/tenacity.egg-info → tenacity-8.4.1}/PKG-INFO +1 -1
- {tenacity-8.3.0 → tenacity-8.4.1}/README.rst +13 -7
- {tenacity-8.3.0 → tenacity-8.4.1}/doc/source/index.rst +13 -7
- tenacity-8.4.1/releasenotes/notes/add-async-actions-b249c527d99723bb.yaml +5 -0
- tenacity-8.4.1/releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml +3 -0
- tenacity-8.4.1/releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml +6 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/setup.cfg +2 -1
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/__init__.py +19 -9
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/_utils.py +12 -0
- tenacity-8.3.0/tenacity/_asyncio.py → tenacity-8.4.1/tenacity/asyncio/__init__.py +77 -24
- tenacity-8.4.1/tenacity/asyncio/retry.py +125 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/retry.py +8 -2
- {tenacity-8.3.0 → tenacity-8.4.1/tenacity.egg-info}/PKG-INFO +1 -1
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity.egg-info/SOURCES.txt +5 -1
- {tenacity-8.3.0 → tenacity-8.4.1}/tests/test_asyncio.py +186 -3
- {tenacity-8.3.0 → tenacity-8.4.1}/tox.ini +4 -2
- {tenacity-8.3.0 → tenacity-8.4.1}/.editorconfig +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/.github/dependabot.yml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/.gitignore +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/.readthedocs.yml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/LICENSE +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/doc/source/api.rst +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/doc/source/changelog.rst +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/doc/source/conf.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/pyproject.toml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/add-reno-d1ab5710f272650a.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/add-test-extra-55e869261b03e56d.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/add_retry_if_exception_cause_type-d16b918ace4ae0ad.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/added_a_link_to_documentation-eefaf8f074b539f8.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/after_log-50f4d73b24ce9203.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/annotate_code-197b93130df14042.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/before_sleep_log-improvements-d8149274dfb37d7c.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/dependabot-for-github-actions-4d2464f3c0928463.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/make-logger-more-compatible-5da1ddf1bab77047.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/no-async-iter-6132a42e52348a75.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/pr320-py3-only-wheel-tag.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/py36_plus-c425fb3aa17c6682.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/remove-py36-876c0416cf279d15.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/reno.yaml +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/setup.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/after.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/before.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/before_sleep.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/nap.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/py.typed +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/stop.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/tornadoweb.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity/wait.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity.egg-info/dependency_links.txt +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity.egg-info/requires.txt +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tenacity.egg-info/top_level.txt +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tests/__init__.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tests/test_after.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tests/test_tenacity.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tests/test_tornado.py +0 -0
- {tenacity-8.3.0 → tenacity-8.4.1}/tests/test_utils.py +0 -0
|
@@ -27,19 +27,19 @@ jobs:
|
|
|
27
27
|
- python: "3.11"
|
|
28
28
|
tox: py311
|
|
29
29
|
- python: "3.12"
|
|
30
|
-
tox: py312
|
|
30
|
+
tox: py312,py312-trio
|
|
31
31
|
- python: "3.12"
|
|
32
32
|
tox: pep8
|
|
33
33
|
- python: "3.11"
|
|
34
34
|
tox: mypy
|
|
35
35
|
steps:
|
|
36
36
|
- name: Checkout 🛎️
|
|
37
|
-
uses: actions/checkout@v4.1.
|
|
37
|
+
uses: actions/checkout@v4.1.6
|
|
38
38
|
with:
|
|
39
39
|
fetch-depth: 0
|
|
40
40
|
|
|
41
41
|
- name: Setup Python 🔧
|
|
42
|
-
uses: actions/setup-python@v5.
|
|
42
|
+
uses: actions/setup-python@v5.1.0
|
|
43
43
|
with:
|
|
44
44
|
python-version: ${{ matrix.python }}
|
|
45
45
|
allow-prereleases: true
|
|
@@ -11,12 +11,12 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
13
|
- name: Checkout 🛎️
|
|
14
|
-
uses: actions/checkout@v4.1.
|
|
14
|
+
uses: actions/checkout@v4.1.6
|
|
15
15
|
with:
|
|
16
16
|
fetch-depth: 0
|
|
17
17
|
|
|
18
18
|
- name: Setup Python 🔧
|
|
19
|
-
uses: actions/setup-python@v5.
|
|
19
|
+
uses: actions/setup-python@v5.1.0
|
|
20
20
|
with:
|
|
21
21
|
python-version: 3.11
|
|
22
22
|
|
|
@@ -14,7 +14,7 @@ queue_rules:
|
|
|
14
14
|
- "check-success=test (3.9, py39)"
|
|
15
15
|
- "check-success=test (3.10, py310)"
|
|
16
16
|
- "check-success=test (3.11, py311)"
|
|
17
|
-
- "check-success=test (3.12, py312)"
|
|
17
|
+
- "check-success=test (3.12, py312,py312-trio)"
|
|
18
18
|
- "check-success=test (3.12, pep8)"
|
|
19
19
|
|
|
20
20
|
pull_request_rules:
|
|
@@ -79,7 +79,7 @@ Examples
|
|
|
79
79
|
Basic Retry
|
|
80
80
|
~~~~~~~~~~~
|
|
81
81
|
|
|
82
|
-
.. testsetup::
|
|
82
|
+
.. testsetup::
|
|
83
83
|
|
|
84
84
|
import logging
|
|
85
85
|
#
|
|
@@ -568,28 +568,34 @@ in retry strategies like ``retry_if_result``. This can be done accessing the
|
|
|
568
568
|
Async and retry
|
|
569
569
|
~~~~~~~~~~~~~~~
|
|
570
570
|
|
|
571
|
-
Finally, ``retry`` works also on asyncio and Tornado (>= 4.5) coroutines.
|
|
571
|
+
Finally, ``retry`` works also on asyncio, Trio, and Tornado (>= 4.5) coroutines.
|
|
572
572
|
Sleeps are done asynchronously too.
|
|
573
573
|
|
|
574
574
|
.. code-block:: python
|
|
575
575
|
|
|
576
576
|
@retry
|
|
577
|
-
async def
|
|
577
|
+
async def my_asyncio_function(loop):
|
|
578
578
|
await loop.getaddrinfo('8.8.8.8', 53)
|
|
579
579
|
|
|
580
|
+
.. code-block:: python
|
|
581
|
+
|
|
582
|
+
@retry
|
|
583
|
+
async def my_async_trio_function():
|
|
584
|
+
await trio.socket.getaddrinfo('8.8.8.8', 53)
|
|
585
|
+
|
|
580
586
|
.. code-block:: python
|
|
581
587
|
|
|
582
588
|
@retry
|
|
583
589
|
@tornado.gen.coroutine
|
|
584
|
-
def
|
|
590
|
+
def my_async_tornado_function(http_client, url):
|
|
585
591
|
yield http_client.fetch(url)
|
|
586
592
|
|
|
587
|
-
You can even use alternative event loops such as `curio`
|
|
593
|
+
You can even use alternative event loops such as `curio` by passing the correct sleep function:
|
|
588
594
|
|
|
589
595
|
.. code-block:: python
|
|
590
596
|
|
|
591
|
-
@retry(sleep=
|
|
592
|
-
async def
|
|
597
|
+
@retry(sleep=curio.sleep)
|
|
598
|
+
async def my_async_curio_function():
|
|
593
599
|
await asks.get('https://example.org')
|
|
594
600
|
|
|
595
601
|
Contribute
|
|
@@ -79,7 +79,7 @@ Examples
|
|
|
79
79
|
Basic Retry
|
|
80
80
|
~~~~~~~~~~~
|
|
81
81
|
|
|
82
|
-
.. testsetup::
|
|
82
|
+
.. testsetup::
|
|
83
83
|
|
|
84
84
|
import logging
|
|
85
85
|
#
|
|
@@ -568,28 +568,34 @@ in retry strategies like ``retry_if_result``. This can be done accessing the
|
|
|
568
568
|
Async and retry
|
|
569
569
|
~~~~~~~~~~~~~~~
|
|
570
570
|
|
|
571
|
-
Finally, ``retry`` works also on asyncio and Tornado (>= 4.5) coroutines.
|
|
571
|
+
Finally, ``retry`` works also on asyncio, Trio, and Tornado (>= 4.5) coroutines.
|
|
572
572
|
Sleeps are done asynchronously too.
|
|
573
573
|
|
|
574
574
|
.. code-block:: python
|
|
575
575
|
|
|
576
576
|
@retry
|
|
577
|
-
async def
|
|
577
|
+
async def my_asyncio_function(loop):
|
|
578
578
|
await loop.getaddrinfo('8.8.8.8', 53)
|
|
579
579
|
|
|
580
|
+
.. code-block:: python
|
|
581
|
+
|
|
582
|
+
@retry
|
|
583
|
+
async def my_async_trio_function():
|
|
584
|
+
await trio.socket.getaddrinfo('8.8.8.8', 53)
|
|
585
|
+
|
|
580
586
|
.. code-block:: python
|
|
581
587
|
|
|
582
588
|
@retry
|
|
583
589
|
@tornado.gen.coroutine
|
|
584
|
-
def
|
|
590
|
+
def my_async_tornado_function(http_client, url):
|
|
585
591
|
yield http_client.fetch(url)
|
|
586
592
|
|
|
587
|
-
You can even use alternative event loops such as `curio`
|
|
593
|
+
You can even use alternative event loops such as `curio` by passing the correct sleep function:
|
|
588
594
|
|
|
589
595
|
.. code-block:: python
|
|
590
596
|
|
|
591
|
-
@retry(sleep=
|
|
592
|
-
async def
|
|
597
|
+
@retry(sleep=curio.sleep)
|
|
598
|
+
async def my_async_curio_function():
|
|
593
599
|
await asks.get('https://example.org')
|
|
594
600
|
|
|
595
601
|
Contribute
|
|
@@ -24,7 +24,8 @@ import typing as t
|
|
|
24
24
|
import warnings
|
|
25
25
|
from abc import ABC, abstractmethod
|
|
26
26
|
from concurrent import futures
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
from . import _utils
|
|
28
29
|
|
|
29
30
|
# Import all built-in retry strategies for easier usage.
|
|
30
31
|
from .retry import retry_base # noqa
|
|
@@ -87,6 +88,7 @@ except ImportError:
|
|
|
87
88
|
if t.TYPE_CHECKING:
|
|
88
89
|
import types
|
|
89
90
|
|
|
91
|
+
from . import asyncio as tasyncio
|
|
90
92
|
from .retry import RetryBaseT
|
|
91
93
|
from .stop import StopBaseT
|
|
92
94
|
from .wait import WaitBaseT
|
|
@@ -593,16 +595,24 @@ def retry(func: WrappedFn) -> WrappedFn: ...
|
|
|
593
595
|
|
|
594
596
|
@t.overload
|
|
595
597
|
def retry(
|
|
596
|
-
sleep: t.Callable[[t.Union[int, float]], t.
|
|
598
|
+
sleep: t.Callable[[t.Union[int, float]], t.Union[None, t.Awaitable[None]]] = sleep,
|
|
597
599
|
stop: "StopBaseT" = stop_never,
|
|
598
600
|
wait: "WaitBaseT" = wait_none(),
|
|
599
|
-
retry: "RetryBaseT" = retry_if_exception_type(),
|
|
600
|
-
before: t.Callable[
|
|
601
|
-
|
|
602
|
-
|
|
601
|
+
retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = retry_if_exception_type(),
|
|
602
|
+
before: t.Callable[
|
|
603
|
+
["RetryCallState"], t.Union[None, t.Awaitable[None]]
|
|
604
|
+
] = before_nothing,
|
|
605
|
+
after: t.Callable[
|
|
606
|
+
["RetryCallState"], t.Union[None, t.Awaitable[None]]
|
|
607
|
+
] = after_nothing,
|
|
608
|
+
before_sleep: t.Optional[
|
|
609
|
+
t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
|
|
610
|
+
] = None,
|
|
603
611
|
reraise: bool = False,
|
|
604
612
|
retry_error_cls: t.Type["RetryError"] = RetryError,
|
|
605
|
-
retry_error_callback: t.Optional[
|
|
613
|
+
retry_error_callback: t.Optional[
|
|
614
|
+
t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
|
|
615
|
+
] = None,
|
|
606
616
|
) -> t.Callable[[WrappedFn], WrappedFn]: ...
|
|
607
617
|
|
|
608
618
|
|
|
@@ -624,7 +634,7 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
|
|
|
624
634
|
f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)"
|
|
625
635
|
)
|
|
626
636
|
r: "BaseRetrying"
|
|
627
|
-
if
|
|
637
|
+
if _utils.is_coroutine_callable(f):
|
|
628
638
|
r = AsyncRetrying(*dargs, **dkw)
|
|
629
639
|
elif (
|
|
630
640
|
tornado
|
|
@@ -640,7 +650,7 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
|
|
|
640
650
|
return wrap
|
|
641
651
|
|
|
642
652
|
|
|
643
|
-
from tenacity.
|
|
653
|
+
from tenacity.asyncio import AsyncRetrying # noqa:E402,I100
|
|
644
654
|
|
|
645
655
|
if tornado:
|
|
646
656
|
from tenacity.tornadoweb import TornadoRetrying
|
|
@@ -87,3 +87,15 @@ def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:
|
|
|
87
87
|
partial_call = isinstance(call, functools.partial) and call.func
|
|
88
88
|
dunder_call = partial_call or getattr(call, "__call__", None)
|
|
89
89
|
return inspect.iscoroutinefunction(dunder_call)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def wrap_to_async_func(
|
|
93
|
+
call: typing.Callable[..., typing.Any],
|
|
94
|
+
) -> typing.Callable[..., typing.Awaitable[typing.Any]]:
|
|
95
|
+
if is_coroutine_callable(call):
|
|
96
|
+
return call
|
|
97
|
+
|
|
98
|
+
async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
|
|
99
|
+
return call(*args, **kwargs)
|
|
100
|
+
|
|
101
|
+
return inner
|
|
@@ -19,34 +19,87 @@ import functools
|
|
|
19
19
|
import sys
|
|
20
20
|
import typing as t
|
|
21
21
|
|
|
22
|
+
import tenacity
|
|
22
23
|
from tenacity import AttemptManager
|
|
23
24
|
from tenacity import BaseRetrying
|
|
24
25
|
from tenacity import DoAttempt
|
|
25
26
|
from tenacity import DoSleep
|
|
26
27
|
from tenacity import RetryCallState
|
|
28
|
+
from tenacity import RetryError
|
|
29
|
+
from tenacity import after_nothing
|
|
30
|
+
from tenacity import before_nothing
|
|
27
31
|
from tenacity import _utils
|
|
28
32
|
|
|
33
|
+
# Import all built-in retry strategies for easier usage.
|
|
34
|
+
from .retry import RetryBaseT
|
|
35
|
+
from .retry import retry_all # noqa
|
|
36
|
+
from .retry import retry_any # noqa
|
|
37
|
+
from .retry import retry_if_exception # noqa
|
|
38
|
+
from .retry import retry_if_result # noqa
|
|
39
|
+
from ..retry import RetryBaseT as SyncRetryBaseT
|
|
40
|
+
|
|
41
|
+
if t.TYPE_CHECKING:
|
|
42
|
+
from tenacity.stop import StopBaseT
|
|
43
|
+
from tenacity.wait import WaitBaseT
|
|
44
|
+
|
|
29
45
|
WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
|
|
30
46
|
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
|
|
31
47
|
|
|
32
48
|
|
|
33
|
-
def
|
|
49
|
+
def _portable_async_sleep(seconds: float) -> t.Awaitable[None]:
|
|
50
|
+
# If trio is already imported, then importing it is cheap.
|
|
51
|
+
# If trio isn't already imported, then it's definitely not running, so we
|
|
52
|
+
# can skip further checks.
|
|
53
|
+
if "trio" in sys.modules:
|
|
54
|
+
# If trio is available, then sniffio is too
|
|
55
|
+
import trio
|
|
56
|
+
import sniffio
|
|
57
|
+
|
|
58
|
+
if sniffio.current_async_library() == "trio":
|
|
59
|
+
return trio.sleep(seconds)
|
|
60
|
+
# Otherwise, assume asyncio
|
|
34
61
|
# Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).
|
|
35
62
|
import asyncio
|
|
36
63
|
|
|
37
|
-
return asyncio.sleep(
|
|
64
|
+
return asyncio.sleep(seconds)
|
|
38
65
|
|
|
39
66
|
|
|
40
67
|
class AsyncRetrying(BaseRetrying):
|
|
41
|
-
sleep: t.Callable[[float], t.Awaitable[t.Any]]
|
|
42
|
-
|
|
43
68
|
def __init__(
|
|
44
69
|
self,
|
|
45
|
-
sleep: t.Callable[
|
|
46
|
-
|
|
70
|
+
sleep: t.Callable[
|
|
71
|
+
[t.Union[int, float]], t.Union[None, t.Awaitable[None]]
|
|
72
|
+
] = _portable_async_sleep,
|
|
73
|
+
stop: "StopBaseT" = tenacity.stop.stop_never,
|
|
74
|
+
wait: "WaitBaseT" = tenacity.wait.wait_none(),
|
|
75
|
+
retry: "t.Union[SyncRetryBaseT, RetryBaseT]" = tenacity.retry_if_exception_type(),
|
|
76
|
+
before: t.Callable[
|
|
77
|
+
["RetryCallState"], t.Union[None, t.Awaitable[None]]
|
|
78
|
+
] = before_nothing,
|
|
79
|
+
after: t.Callable[
|
|
80
|
+
["RetryCallState"], t.Union[None, t.Awaitable[None]]
|
|
81
|
+
] = after_nothing,
|
|
82
|
+
before_sleep: t.Optional[
|
|
83
|
+
t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
|
|
84
|
+
] = None,
|
|
85
|
+
reraise: bool = False,
|
|
86
|
+
retry_error_cls: t.Type["RetryError"] = RetryError,
|
|
87
|
+
retry_error_callback: t.Optional[
|
|
88
|
+
t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
|
|
89
|
+
] = None,
|
|
47
90
|
) -> None:
|
|
48
|
-
super().__init__(
|
|
49
|
-
|
|
91
|
+
super().__init__(
|
|
92
|
+
sleep=sleep, # type: ignore[arg-type]
|
|
93
|
+
stop=stop,
|
|
94
|
+
wait=wait,
|
|
95
|
+
retry=retry, # type: ignore[arg-type]
|
|
96
|
+
before=before, # type: ignore[arg-type]
|
|
97
|
+
after=after, # type: ignore[arg-type]
|
|
98
|
+
before_sleep=before_sleep, # type: ignore[arg-type]
|
|
99
|
+
reraise=reraise,
|
|
100
|
+
retry_error_cls=retry_error_cls,
|
|
101
|
+
retry_error_callback=retry_error_callback,
|
|
102
|
+
)
|
|
50
103
|
|
|
51
104
|
async def __call__( # type: ignore[override]
|
|
52
105
|
self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any
|
|
@@ -65,31 +118,21 @@ class AsyncRetrying(BaseRetrying):
|
|
|
65
118
|
retry_state.set_result(result)
|
|
66
119
|
elif isinstance(do, DoSleep):
|
|
67
120
|
retry_state.prepare_for_next_attempt()
|
|
68
|
-
await self.sleep(do)
|
|
121
|
+
await self.sleep(do) # type: ignore[misc]
|
|
69
122
|
else:
|
|
70
123
|
return do # type: ignore[no-any-return]
|
|
71
124
|
|
|
72
|
-
@classmethod
|
|
73
|
-
def _wrap_action_func(cls, fn: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
|
|
74
|
-
if _utils.is_coroutine_callable(fn):
|
|
75
|
-
return fn
|
|
76
|
-
|
|
77
|
-
async def inner(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
|
78
|
-
return fn(*args, **kwargs)
|
|
79
|
-
|
|
80
|
-
return inner
|
|
81
|
-
|
|
82
125
|
def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
|
|
83
|
-
self.iter_state.actions.append(
|
|
126
|
+
self.iter_state.actions.append(_utils.wrap_to_async_func(fn))
|
|
84
127
|
|
|
85
128
|
async def _run_retry(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
|
|
86
|
-
self.iter_state.retry_run_result = await
|
|
129
|
+
self.iter_state.retry_run_result = await _utils.wrap_to_async_func(self.retry)(
|
|
87
130
|
retry_state
|
|
88
131
|
)
|
|
89
132
|
|
|
90
133
|
async def _run_wait(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
|
|
91
134
|
if self.wait:
|
|
92
|
-
sleep = await
|
|
135
|
+
sleep = await _utils.wrap_to_async_func(self.wait)(retry_state)
|
|
93
136
|
else:
|
|
94
137
|
sleep = 0.0
|
|
95
138
|
|
|
@@ -97,7 +140,7 @@ class AsyncRetrying(BaseRetrying):
|
|
|
97
140
|
|
|
98
141
|
async def _run_stop(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
|
|
99
142
|
self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
|
|
100
|
-
self.iter_state.stop_run_result = await
|
|
143
|
+
self.iter_state.stop_run_result = await _utils.wrap_to_async_func(self.stop)(
|
|
101
144
|
retry_state
|
|
102
145
|
)
|
|
103
146
|
|
|
@@ -127,7 +170,7 @@ class AsyncRetrying(BaseRetrying):
|
|
|
127
170
|
return AttemptManager(retry_state=self._retry_state)
|
|
128
171
|
elif isinstance(do, DoSleep):
|
|
129
172
|
self._retry_state.prepare_for_next_attempt()
|
|
130
|
-
await self.sleep(do)
|
|
173
|
+
await self.sleep(do) # type: ignore[misc]
|
|
131
174
|
else:
|
|
132
175
|
raise StopAsyncIteration
|
|
133
176
|
|
|
@@ -146,3 +189,13 @@ class AsyncRetrying(BaseRetrying):
|
|
|
146
189
|
async_wrapped.retry_with = fn.retry_with # type: ignore[attr-defined]
|
|
147
190
|
|
|
148
191
|
return async_wrapped # type: ignore[return-value]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
__all__ = [
|
|
195
|
+
"retry_all",
|
|
196
|
+
"retry_any",
|
|
197
|
+
"retry_if_exception",
|
|
198
|
+
"retry_if_result",
|
|
199
|
+
"WrappedFn",
|
|
200
|
+
"AsyncRetrying",
|
|
201
|
+
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Copyright 2016–2021 Julien Danjou
|
|
2
|
+
# Copyright 2016 Joshua Harlow
|
|
3
|
+
# Copyright 2013-2014 Ray Holder
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
import abc
|
|
17
|
+
import typing
|
|
18
|
+
|
|
19
|
+
from tenacity import _utils
|
|
20
|
+
from tenacity import retry_base
|
|
21
|
+
|
|
22
|
+
if typing.TYPE_CHECKING:
|
|
23
|
+
from tenacity import RetryCallState
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class async_retry_base(retry_base):
|
|
27
|
+
"""Abstract base class for async retry strategies."""
|
|
28
|
+
|
|
29
|
+
@abc.abstractmethod
|
|
30
|
+
async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def __and__( # type: ignore[override]
|
|
34
|
+
self, other: "typing.Union[retry_base, async_retry_base]"
|
|
35
|
+
) -> "retry_all":
|
|
36
|
+
return retry_all(self, other)
|
|
37
|
+
|
|
38
|
+
def __rand__( # type: ignore[misc,override]
|
|
39
|
+
self, other: "typing.Union[retry_base, async_retry_base]"
|
|
40
|
+
) -> "retry_all":
|
|
41
|
+
return retry_all(other, self)
|
|
42
|
+
|
|
43
|
+
def __or__( # type: ignore[override]
|
|
44
|
+
self, other: "typing.Union[retry_base, async_retry_base]"
|
|
45
|
+
) -> "retry_any":
|
|
46
|
+
return retry_any(self, other)
|
|
47
|
+
|
|
48
|
+
def __ror__( # type: ignore[misc,override]
|
|
49
|
+
self, other: "typing.Union[retry_base, async_retry_base]"
|
|
50
|
+
) -> "retry_any":
|
|
51
|
+
return retry_any(other, self)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
RetryBaseT = typing.Union[
|
|
55
|
+
async_retry_base, typing.Callable[["RetryCallState"], typing.Awaitable[bool]]
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class retry_if_exception(async_retry_base):
|
|
60
|
+
"""Retry strategy that retries if an exception verifies a predicate."""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self, predicate: typing.Callable[[BaseException], typing.Awaitable[bool]]
|
|
64
|
+
) -> None:
|
|
65
|
+
self.predicate = predicate
|
|
66
|
+
|
|
67
|
+
async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
|
|
68
|
+
if retry_state.outcome is None:
|
|
69
|
+
raise RuntimeError("__call__() called before outcome was set")
|
|
70
|
+
|
|
71
|
+
if retry_state.outcome.failed:
|
|
72
|
+
exception = retry_state.outcome.exception()
|
|
73
|
+
if exception is None:
|
|
74
|
+
raise RuntimeError("outcome failed but the exception is None")
|
|
75
|
+
return await self.predicate(exception)
|
|
76
|
+
else:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class retry_if_result(async_retry_base):
|
|
81
|
+
"""Retries if the result verifies a predicate."""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self, predicate: typing.Callable[[typing.Any], typing.Awaitable[bool]]
|
|
85
|
+
) -> None:
|
|
86
|
+
self.predicate = predicate
|
|
87
|
+
|
|
88
|
+
async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
|
|
89
|
+
if retry_state.outcome is None:
|
|
90
|
+
raise RuntimeError("__call__() called before outcome was set")
|
|
91
|
+
|
|
92
|
+
if not retry_state.outcome.failed:
|
|
93
|
+
return await self.predicate(retry_state.outcome.result())
|
|
94
|
+
else:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class retry_any(async_retry_base):
|
|
99
|
+
"""Retries if any of the retries condition is valid."""
|
|
100
|
+
|
|
101
|
+
def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None:
|
|
102
|
+
self.retries = retries
|
|
103
|
+
|
|
104
|
+
async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
|
|
105
|
+
result = False
|
|
106
|
+
for r in self.retries:
|
|
107
|
+
result = result or await _utils.wrap_to_async_func(r)(retry_state)
|
|
108
|
+
if result:
|
|
109
|
+
break
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class retry_all(async_retry_base):
|
|
114
|
+
"""Retries if all the retries condition are valid."""
|
|
115
|
+
|
|
116
|
+
def __init__(self, *retries: typing.Union[retry_base, async_retry_base]) -> None:
|
|
117
|
+
self.retries = retries
|
|
118
|
+
|
|
119
|
+
async def __call__(self, retry_state: "RetryCallState") -> bool: # type: ignore[override]
|
|
120
|
+
result = True
|
|
121
|
+
for r in self.retries:
|
|
122
|
+
result = result and await _utils.wrap_to_async_func(r)(retry_state)
|
|
123
|
+
if not result:
|
|
124
|
+
break
|
|
125
|
+
return result
|
|
@@ -30,10 +30,16 @@ class retry_base(abc.ABC):
|
|
|
30
30
|
pass
|
|
31
31
|
|
|
32
32
|
def __and__(self, other: "retry_base") -> "retry_all":
|
|
33
|
-
return
|
|
33
|
+
return other.__rand__(self)
|
|
34
|
+
|
|
35
|
+
def __rand__(self, other: "retry_base") -> "retry_all":
|
|
36
|
+
return retry_all(other, self)
|
|
34
37
|
|
|
35
38
|
def __or__(self, other: "retry_base") -> "retry_any":
|
|
36
|
-
return
|
|
39
|
+
return other.__ror__(self)
|
|
40
|
+
|
|
41
|
+
def __ror__(self, other: "retry_base") -> "retry_any":
|
|
42
|
+
return retry_any(other, self)
|
|
37
43
|
|
|
38
44
|
|
|
39
45
|
RetryBaseT = typing.Union[retry_base, typing.Callable[["RetryCallState"], bool]]
|
|
@@ -18,6 +18,7 @@ doc/source/conf.py
|
|
|
18
18
|
doc/source/index.rst
|
|
19
19
|
releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml
|
|
20
20
|
releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml
|
|
21
|
+
releasenotes/notes/add-async-actions-b249c527d99723bb.yaml
|
|
21
22
|
releasenotes/notes/add-reno-d1ab5710f272650a.yaml
|
|
22
23
|
releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml
|
|
23
24
|
releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml
|
|
@@ -36,6 +37,7 @@ releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml
|
|
|
36
37
|
releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml
|
|
37
38
|
releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml
|
|
38
39
|
releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml
|
|
40
|
+
releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml
|
|
39
41
|
releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml
|
|
40
42
|
releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml
|
|
41
43
|
releasenotes/notes/make-logger-more-compatible-5da1ddf1bab77047.yaml
|
|
@@ -48,9 +50,9 @@ releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml
|
|
|
48
50
|
releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml
|
|
49
51
|
releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml
|
|
50
52
|
releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml
|
|
53
|
+
releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml
|
|
51
54
|
releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml
|
|
52
55
|
tenacity/__init__.py
|
|
53
|
-
tenacity/_asyncio.py
|
|
54
56
|
tenacity/_utils.py
|
|
55
57
|
tenacity/after.py
|
|
56
58
|
tenacity/before.py
|
|
@@ -66,6 +68,8 @@ tenacity.egg-info/SOURCES.txt
|
|
|
66
68
|
tenacity.egg-info/dependency_links.txt
|
|
67
69
|
tenacity.egg-info/requires.txt
|
|
68
70
|
tenacity.egg-info/top_level.txt
|
|
71
|
+
tenacity/asyncio/__init__.py
|
|
72
|
+
tenacity/asyncio/retry.py
|
|
69
73
|
tests/__init__.py
|
|
70
74
|
tests/test_after.py
|
|
71
75
|
tests/test_asyncio.py
|
|
@@ -18,12 +18,19 @@ import inspect
|
|
|
18
18
|
import unittest
|
|
19
19
|
from functools import wraps
|
|
20
20
|
|
|
21
|
+
try:
|
|
22
|
+
import trio
|
|
23
|
+
except ImportError:
|
|
24
|
+
have_trio = False
|
|
25
|
+
else:
|
|
26
|
+
have_trio = True
|
|
27
|
+
|
|
21
28
|
import pytest
|
|
22
29
|
|
|
23
30
|
import tenacity
|
|
24
31
|
from tenacity import AsyncRetrying, RetryError
|
|
25
|
-
from tenacity import
|
|
26
|
-
from tenacity import retry, retry_if_result, stop_after_attempt
|
|
32
|
+
from tenacity import asyncio as tasyncio
|
|
33
|
+
from tenacity import retry, retry_if_exception, retry_if_result, stop_after_attempt
|
|
27
34
|
from tenacity.wait import wait_fixed
|
|
28
35
|
|
|
29
36
|
from .test_tenacity import NoIOErrorAfterCount, current_time_ms
|
|
@@ -55,7 +62,7 @@ async def _retryable_coroutine_with_2_attempts(thing):
|
|
|
55
62
|
thing.go()
|
|
56
63
|
|
|
57
64
|
|
|
58
|
-
class
|
|
65
|
+
class TestAsyncio(unittest.TestCase):
|
|
59
66
|
@asynctest
|
|
60
67
|
async def test_retry(self):
|
|
61
68
|
thing = NoIOErrorAfterCount(5)
|
|
@@ -138,6 +145,21 @@ class TestAsync(unittest.TestCase):
|
|
|
138
145
|
assert list(attempt_nos2) == [1, 2, 3]
|
|
139
146
|
|
|
140
147
|
|
|
148
|
+
@unittest.skipIf(not have_trio, "trio not installed")
|
|
149
|
+
class TestTrio(unittest.TestCase):
|
|
150
|
+
def test_trio_basic(self):
|
|
151
|
+
thing = NoIOErrorAfterCount(5)
|
|
152
|
+
|
|
153
|
+
@retry
|
|
154
|
+
async def trio_function():
|
|
155
|
+
await trio.sleep(0.00001)
|
|
156
|
+
return thing.go()
|
|
157
|
+
|
|
158
|
+
trio.run(trio_function)
|
|
159
|
+
|
|
160
|
+
assert thing.counter == thing.count
|
|
161
|
+
|
|
162
|
+
|
|
141
163
|
class TestContextManager(unittest.TestCase):
|
|
142
164
|
@asynctest
|
|
143
165
|
async def test_do_max_attempts(self):
|
|
@@ -202,6 +224,167 @@ class TestContextManager(unittest.TestCase):
|
|
|
202
224
|
|
|
203
225
|
self.assertEqual(3, result)
|
|
204
226
|
|
|
227
|
+
@asynctest
|
|
228
|
+
async def test_retry_with_async_result(self):
|
|
229
|
+
async def test():
|
|
230
|
+
attempts = 0
|
|
231
|
+
|
|
232
|
+
async def lt_3(x: float) -> bool:
|
|
233
|
+
return x < 3
|
|
234
|
+
|
|
235
|
+
async for attempt in tasyncio.AsyncRetrying(
|
|
236
|
+
retry=tasyncio.retry_if_result(lt_3)
|
|
237
|
+
):
|
|
238
|
+
with attempt:
|
|
239
|
+
attempts += 1
|
|
240
|
+
|
|
241
|
+
assert attempt.retry_state.outcome # help mypy
|
|
242
|
+
if not attempt.retry_state.outcome.failed:
|
|
243
|
+
attempt.retry_state.set_result(attempts)
|
|
244
|
+
|
|
245
|
+
return attempts
|
|
246
|
+
|
|
247
|
+
result = await test()
|
|
248
|
+
|
|
249
|
+
self.assertEqual(3, result)
|
|
250
|
+
|
|
251
|
+
@asynctest
|
|
252
|
+
async def test_retry_with_async_exc(self):
|
|
253
|
+
async def test():
|
|
254
|
+
attempts = 0
|
|
255
|
+
|
|
256
|
+
class CustomException(Exception):
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
async def is_exc(e: BaseException) -> bool:
|
|
260
|
+
return isinstance(e, CustomException)
|
|
261
|
+
|
|
262
|
+
async for attempt in tasyncio.AsyncRetrying(
|
|
263
|
+
retry=tasyncio.retry_if_exception(is_exc)
|
|
264
|
+
):
|
|
265
|
+
with attempt:
|
|
266
|
+
attempts += 1
|
|
267
|
+
if attempts < 3:
|
|
268
|
+
raise CustomException()
|
|
269
|
+
|
|
270
|
+
assert attempt.retry_state.outcome # help mypy
|
|
271
|
+
if not attempt.retry_state.outcome.failed:
|
|
272
|
+
attempt.retry_state.set_result(attempts)
|
|
273
|
+
|
|
274
|
+
return attempts
|
|
275
|
+
|
|
276
|
+
result = await test()
|
|
277
|
+
|
|
278
|
+
self.assertEqual(3, result)
|
|
279
|
+
|
|
280
|
+
@asynctest
|
|
281
|
+
async def test_retry_with_async_result_or(self):
|
|
282
|
+
async def test():
|
|
283
|
+
attempts = 0
|
|
284
|
+
|
|
285
|
+
async def lt_3(x: float) -> bool:
|
|
286
|
+
return x < 3
|
|
287
|
+
|
|
288
|
+
class CustomException(Exception):
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
def is_exc(e: BaseException) -> bool:
|
|
292
|
+
return isinstance(e, CustomException)
|
|
293
|
+
|
|
294
|
+
retry_strategy = tasyncio.retry_if_result(lt_3) | retry_if_exception(is_exc)
|
|
295
|
+
async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):
|
|
296
|
+
with attempt:
|
|
297
|
+
attempts += 1
|
|
298
|
+
if 2 < attempts < 4:
|
|
299
|
+
raise CustomException()
|
|
300
|
+
|
|
301
|
+
assert attempt.retry_state.outcome # help mypy
|
|
302
|
+
if not attempt.retry_state.outcome.failed:
|
|
303
|
+
attempt.retry_state.set_result(attempts)
|
|
304
|
+
|
|
305
|
+
return attempts
|
|
306
|
+
|
|
307
|
+
result = await test()
|
|
308
|
+
|
|
309
|
+
self.assertEqual(4, result)
|
|
310
|
+
|
|
311
|
+
@asynctest
|
|
312
|
+
async def test_retry_with_async_result_ror(self):
|
|
313
|
+
async def test():
|
|
314
|
+
attempts = 0
|
|
315
|
+
|
|
316
|
+
def lt_3(x: float) -> bool:
|
|
317
|
+
return x < 3
|
|
318
|
+
|
|
319
|
+
class CustomException(Exception):
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
async def is_exc(e: BaseException) -> bool:
|
|
323
|
+
return isinstance(e, CustomException)
|
|
324
|
+
|
|
325
|
+
retry_strategy = retry_if_result(lt_3) | tasyncio.retry_if_exception(is_exc)
|
|
326
|
+
async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):
|
|
327
|
+
with attempt:
|
|
328
|
+
attempts += 1
|
|
329
|
+
if 2 < attempts < 4:
|
|
330
|
+
raise CustomException()
|
|
331
|
+
|
|
332
|
+
assert attempt.retry_state.outcome # help mypy
|
|
333
|
+
if not attempt.retry_state.outcome.failed:
|
|
334
|
+
attempt.retry_state.set_result(attempts)
|
|
335
|
+
|
|
336
|
+
return attempts
|
|
337
|
+
|
|
338
|
+
result = await test()
|
|
339
|
+
|
|
340
|
+
self.assertEqual(4, result)
|
|
341
|
+
|
|
342
|
+
@asynctest
|
|
343
|
+
async def test_retry_with_async_result_and(self):
|
|
344
|
+
async def test():
|
|
345
|
+
attempts = 0
|
|
346
|
+
|
|
347
|
+
async def lt_3(x: float) -> bool:
|
|
348
|
+
return x < 3
|
|
349
|
+
|
|
350
|
+
def gt_0(x: float) -> bool:
|
|
351
|
+
return x > 0
|
|
352
|
+
|
|
353
|
+
retry_strategy = tasyncio.retry_if_result(lt_3) & retry_if_result(gt_0)
|
|
354
|
+
async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):
|
|
355
|
+
with attempt:
|
|
356
|
+
attempts += 1
|
|
357
|
+
attempt.retry_state.set_result(attempts)
|
|
358
|
+
|
|
359
|
+
return attempts
|
|
360
|
+
|
|
361
|
+
result = await test()
|
|
362
|
+
|
|
363
|
+
self.assertEqual(3, result)
|
|
364
|
+
|
|
365
|
+
@asynctest
|
|
366
|
+
async def test_retry_with_async_result_rand(self):
|
|
367
|
+
async def test():
|
|
368
|
+
attempts = 0
|
|
369
|
+
|
|
370
|
+
async def lt_3(x: float) -> bool:
|
|
371
|
+
return x < 3
|
|
372
|
+
|
|
373
|
+
def gt_0(x: float) -> bool:
|
|
374
|
+
return x > 0
|
|
375
|
+
|
|
376
|
+
retry_strategy = retry_if_result(gt_0) & tasyncio.retry_if_result(lt_3)
|
|
377
|
+
async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):
|
|
378
|
+
with attempt:
|
|
379
|
+
attempts += 1
|
|
380
|
+
attempt.retry_state.set_result(attempts)
|
|
381
|
+
|
|
382
|
+
return attempts
|
|
383
|
+
|
|
384
|
+
result = await test()
|
|
385
|
+
|
|
386
|
+
self.assertEqual(3, result)
|
|
387
|
+
|
|
205
388
|
@asynctest
|
|
206
389
|
async def test_async_retying_iterator(self):
|
|
207
390
|
thing = NoIOErrorAfterCount(5)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[tox]
|
|
2
|
-
envlist = py3{8,9,10,11,12}, pep8, pypy3
|
|
2
|
+
envlist = py3{8,9,10,11,12,12-trio}, pep8, pypy3
|
|
3
3
|
skip_missing_interpreters = True
|
|
4
4
|
|
|
5
5
|
[testenv]
|
|
@@ -8,6 +8,7 @@ sitepackages = False
|
|
|
8
8
|
deps =
|
|
9
9
|
.[test]
|
|
10
10
|
.[doc]
|
|
11
|
+
trio: trio
|
|
11
12
|
commands =
|
|
12
13
|
py3{8,9,10,11,12},pypy3: pytest {posargs}
|
|
13
14
|
py3{8,9,10,11,12},pypy3: sphinx-build -a -E -W -b doctest doc/source doc/build
|
|
@@ -24,10 +25,11 @@ commands =
|
|
|
24
25
|
deps =
|
|
25
26
|
mypy>=1.0.0
|
|
26
27
|
pytest # for stubs
|
|
28
|
+
trio
|
|
27
29
|
commands =
|
|
28
30
|
mypy {posargs}
|
|
29
31
|
|
|
30
32
|
[testenv:reno]
|
|
31
33
|
basepython = python3
|
|
32
34
|
deps = reno
|
|
33
|
-
commands = reno {posargs}
|
|
35
|
+
commands = reno {posargs}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml
RENAMED
|
File without changes
|
{tenacity-8.3.0 → tenacity-8.4.1}/releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|