Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Trio out-of-the-box #295

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ jobs:
command: |
sudo pip install tox
tox -e py39
py39-notrio:
docker:
- image: circleci/python:3.9
steps:
- checkout
- run:
command: |
sudo pip install tox
tox -e py39-notrio
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inconsistent with tox.int (py39-trio for trio and normal python versions for other)

deploy:
docker:
- image: circleci/python:3.9
Expand Down Expand Up @@ -100,6 +109,7 @@ workflows:
- py37
- py38
- py39
- py39-notrio
- deploy:
filters:
tags:
Expand Down
18 changes: 12 additions & 6 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -546,28 +546,34 @@ With async code you can use AsyncRetrying.
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
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
Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/trio-support-62fed9e32ccb62be.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
features:
- |
If you're using `Trio <https://trio.readthedocs.io>`__, then
``@retry`` now works automatically. It's no longer necessary to
pass ``sleep=trio.sleep``.
18 changes: 16 additions & 2 deletions tenacity/_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from asyncio import sleep

from tenacity import AttemptManager
from tenacity import BaseRetrying
Expand All @@ -25,8 +24,23 @@
from tenacity import RetryCallState


async def _portable_async_sleep(seconds):
# 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, sniffio
if sniffio.current_async_library() == "trio":
await trio.sleep(seconds)
return
# Otherwise, assume asyncio
import asyncio
await asyncio.sleep(seconds)


class AsyncRetrying(BaseRetrying):
def __init__(self, sleep=sleep, **kwargs):
def __init__(self, sleep=_portable_async_sleep, **kwargs):
super(AsyncRetrying, self).__init__(**kwargs)
self.sleep = sleep

Expand Down
24 changes: 23 additions & 1 deletion tenacity/tests/test_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
import inspect
import unittest

try:
import trio
except ImportError:
have_trio = False
else:
have_trio = True

import pytest

import six
Expand Down Expand Up @@ -54,7 +61,7 @@ async def _retryable_coroutine_with_2_attempts(thing):
thing.go()


class TestAsync(unittest.TestCase):
class TestAsyncio(unittest.TestCase):
@asynctest
async def test_retry(self):
thing = NoIOErrorAfterCount(5)
Expand Down Expand Up @@ -125,6 +132,21 @@ def after(retry_state):
assert list(attempt_nos2) == [1, 2, 3]


@unittest.skipIf(not have_trio, "trio not installed")
class TestTrio(unittest.TestCase):
def test_trio_basic(self):
thing = NoIOErrorAfterCount(5)

@retry
async def trio_function():
await trio.sleep(0.00001)
return thing.go()

trio.run(trio_function)

assert thing.counter == thing.count


class TestContextManager(unittest.TestCase):
@asynctest
async def test_do_max_attempts(self):
Expand Down
10 changes: 9 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27, py35, py36, py37, py38, py39, pep8, pypy
envlist = py27, py35, py36, py37, py38, py39, py39-trio, pep8, pypy

[testenv]
usedevelop = True
Expand All @@ -14,6 +14,14 @@ commands =
py3{5,6,7,8,9}: sphinx-build -a -E -W -b doctest doc/source doc/build
py3{5,6,7,8,9}: sphinx-build -a -E -W -b html doc/source doc/build

[testenv:py39-trio]
deps =
.[doc]
pytest
trio
typeguard;python_version>='3.0'
commands = pytest {posargs}

[testenv:pep8]
basepython = python3
deps = flake8
Expand Down