Skip to content

Commit ade0567

Browse files
jakkdlnjsmithjd
authored
Support Trio out-of-the-box, take 2 (#463)
* Support Trio out-of-the-box This PR makes `@retry` just work when running under Trio. * Add a no-trio test environment * Switch to only testing trio in one environment * bump releasenote so it is later in history->reno puts it in the correct place in the changelog * fix mypy & pep8 checks * Update doc/source/index.rst fix example Co-authored-by: Julien Danjou <[email protected]> * Update tests/test_tornado.py * Update tests/test_tornado.py * make _portably_async_sleep a sync function that returns an async function --------- Co-authored-by: Nathaniel J. Smith <[email protected]> Co-authored-by: Julien Danjou <[email protected]>
1 parent 952189b commit ade0567

File tree

6 files changed

+60
-13
lines changed

6 files changed

+60
-13
lines changed

.github/workflows/ci.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- python: "3.11"
2828
tox: py311
2929
- python: "3.12"
30-
tox: py312
30+
tox: py312,py312-trio
3131
- python: "3.12"
3232
tox: pep8
3333
- python: "3.11"

doc/source/index.rst

+12-6
Original file line numberDiff line numberDiff line change
@@ -568,28 +568,34 @@ in retry strategies like ``retry_if_result``. This can be done accessing the
568568
Async and retry
569569
~~~~~~~~~~~~~~~
570570

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.
572572
Sleeps are done asynchronously too.
573573

574574
.. code-block:: python
575575
576576
@retry
577-
async def my_async_function(loop):
577+
async def my_asyncio_function(loop):
578578
await loop.getaddrinfo('8.8.8.8', 53)
579579
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+
580586
.. code-block:: python
581587
582588
@retry
583589
@tornado.gen.coroutine
584-
def my_async_function(http_client, url):
590+
def my_async_tornado_function(http_client, url):
585591
yield http_client.fetch(url)
586592
587-
You can even use alternative event loops such as `curio` or `Trio` by passing the correct sleep function:
593+
You can even use alternative event loops such as `curio` by passing the correct sleep function:
588594

589595
.. code-block:: python
590596
591-
@retry(sleep=trio.sleep)
592-
async def my_async_function(loop):
597+
@retry(sleep=curio.sleep)
598+
async def my_async_curio_function():
593599
await asks.get('https://example.org')
594600
595601
Contribute
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
features:
3+
- |
4+
If you're using `Trio <https://trio.readthedocs.io>`__, then
5+
``@retry`` now works automatically. It's no longer necessary to
6+
pass ``sleep=trio.sleep``.

tenacity/asyncio/__init__.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,30 @@
4646
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
4747

4848

49-
def asyncio_sleep(duration: float) -> t.Awaitable[None]:
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
5061
# Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).
5162
import asyncio
5263

53-
return asyncio.sleep(duration)
64+
return asyncio.sleep(seconds)
5465

5566

5667
class AsyncRetrying(BaseRetrying):
5768
def __init__(
5869
self,
5970
sleep: t.Callable[
6071
[t.Union[int, float]], t.Union[None, t.Awaitable[None]]
61-
] = asyncio_sleep,
72+
] = _portable_async_sleep,
6273
stop: "StopBaseT" = tenacity.stop.stop_never,
6374
wait: "WaitBaseT" = tenacity.wait.wait_none(),
6475
retry: "t.Union[SyncRetryBaseT, RetryBaseT]" = tenacity.retry_if_exception_type(),

tests/test_asyncio.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
import unittest
1919
from functools import wraps
2020

21+
try:
22+
import trio
23+
except ImportError:
24+
have_trio = False
25+
else:
26+
have_trio = True
27+
2128
import pytest
2229

2330
import tenacity
@@ -55,7 +62,7 @@ async def _retryable_coroutine_with_2_attempts(thing):
5562
thing.go()
5663

5764

58-
class TestAsync(unittest.TestCase):
65+
class TestAsyncio(unittest.TestCase):
5966
@asynctest
6067
async def test_retry(self):
6168
thing = NoIOErrorAfterCount(5)
@@ -138,6 +145,21 @@ def after(retry_state):
138145
assert list(attempt_nos2) == [1, 2, 3]
139146

140147

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+
141163
class TestContextManager(unittest.TestCase):
142164
@asynctest
143165
async def test_do_max_attempts(self):

tox.ini

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py3{8,9,10,11,12}, pep8, pypy3
2+
envlist = py3{8,9,10,11,12,12-trio}, pep8, pypy3
33
skip_missing_interpreters = True
44

55
[testenv]
@@ -8,6 +8,7 @@ sitepackages = False
88
deps =
99
.[test]
1010
.[doc]
11+
trio: trio
1112
commands =
1213
py3{8,9,10,11,12},pypy3: pytest {posargs}
1314
py3{8,9,10,11,12},pypy3: sphinx-build -a -E -W -b doctest doc/source doc/build
@@ -24,10 +25,11 @@ commands =
2425
deps =
2526
mypy>=1.0.0
2627
pytest # for stubs
28+
trio
2729
commands =
2830
mypy {posargs}
2931

3032
[testenv:reno]
3133
basepython = python3
3234
deps = reno
33-
commands = reno {posargs}
35+
commands = reno {posargs}

0 commit comments

Comments
 (0)