Skip to content

Commit 3bc37c4

Browse files
committed
switch from run_trio method to aio_as_trio wrapper
1 parent 604e672 commit 3bc37c4

7 files changed

Lines changed: 332 additions & 105 deletions

File tree

docs/source/usage.rst

Lines changed: 67 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -378,56 +378,87 @@ Thus, you really should not use it for "real" programs or libraries.
378378
Calling Trio from asyncio
379379
+++++++++++++++++++++++++
380380

381-
For basic async calls, pass the function and any arguments to
382-
``loop.run_trio()``. This method returns a standard asyncio Future which
383-
you can await, add callbacks to, or whatever.
381+
Wrap the callable, awaitable, generator, or iterator in :func:`trio_as_aio`.
384382

385-
::
383+
Thus, you can call a :mod:`trio` function from :mod:`asyncio` thus::
386384

387-
async def some_trio_code(foo):
388-
await trio.sleep(1)
389-
return foo*2
390-
391-
future = loop.run_trio(some_trio_code, 21)
392-
res = await future
393-
assert res == 42
385+
async def trio_sleep(sec=1):
386+
await trio.sleep(sec)
387+
async def aio_sleep(sec=2):
388+
await aio_as_trio(trio_sleep)(sec)
389+
trio_asyncio.run(aio_as_trio, aio_sleep, 3)
394390

395-
You can also use the ``aio2trio`` decorator::
391+
or pre-wrapped::
396392

397-
@aio2trio
398-
async def some_trio_code(self, foo):
399-
await trio.sleep(1)
400-
return foo+33
393+
@trio_as_aio
394+
async def trio_sleep(sec=1):
395+
await trio.sleep(sec)
396+
async def aio_sleep(sec=2):
397+
await trio_sleep(sec)
398+
trio_asyncio.run(aio_as_trio, aio_sleep, 3)
401399

402-
res = await some_trio_code(9)
403-
assert res == 42
400+
In contrast to :func:`aio_as_trio`, using an awaitable is not supported
401+
because that's not an idiom :mod:`trio` uses.
404402

405-
.. autodoc: trio_asyncio.adapter.aio2trio
403+
Calling a function wrapped with :func:`trio_as_aio` returns a regular
404+
:class:`asyncio.Future`. Thus, you can call it from a synchronous context
405+
(e.g. a callback hook). Of course, you're responsible for catching any
406+
errors – either arrange to ``await`` the future, or use
407+
``.add_done_callback()`` ::
406408

407-
.. autodoc: trio_asyncio.adapter.aio2trio_task
409+
async def trio_sleep(sec=1):
410+
await trio.sleep(sec)
411+
return 42
412+
def cb(f):
413+
assert f.result == 42
414+
async def aio_sleep(sec=2):
415+
f = trio_as_aio(trio_sleep)(1)
416+
f.add_done_callback(cb)
417+
r = await f
418+
assert r == 42
419+
trio_asyncio.run(aio_as_trio, aio_sleep, 3)
408420

409-
``run_trio()`` is not itself an async function, so you can call it from a
410-
synchronous context (e.g. a callback hook). However, you're responsible for
411-
catching any errors – either await() the future, or use
412-
``.add_done_callback()``.
421+
You can wrap context handlers::
413422

414-
If you want to start a task that shall be monitored by ``trio_asyncio``
415-
(i.e. an uncaught error will propagate to, and terminate, the loop), use
416-
``run_trio_task()`` instead.
423+
class TrioCtx:
424+
async def __aenter__(self):
425+
await trio.sleep(1)
426+
return self
427+
async def __aexit__(self, *tb):
428+
await trio.sleep(1)
429+
async def delay(self, sec=1):
430+
await trio.sleep(sec)
431+
async def aio_ctx():
432+
async with trio_as_aio(AsyncCtx()) as ctx:
433+
print("within")
434+
await trio_as_aio(ctx.delay)(2)
435+
trio_asyncio.run(aio_as_trio, aio_ctx)
436+
437+
You can also wrap async generators or iterators::
438+
439+
async def trio_slow():
440+
n = 0
441+
while True:
442+
await trio.sleep(n)
443+
yield n
444+
n += 1
445+
async def printer():
446+
async for n in trio_as_aio(aio_slow()):
447+
print(n)
448+
trio_asyncio.run(aio_as_trio, printer)
417449

418-
.. autodoc: trio_asyncio.adapter.aio2trio_task
450+
.. autodoc: trio_asyncio.trio_as_aio
419451
420-
If you need to call a Trio-style async context manager from asyncio, use
421-
``loop.wrap_trio_context()``::
422452
423-
async with loop.wrap_trio_context(context()) as ctx:
424-
await loop.run_trio(ctx.do_whatever)
453+
Trio background tasks
454+
---------------------
425455

426-
As you can see from this example, the context that's returned is a "native"
427-
Trio context, so you still need to use ``run_trio()`` if you call its
428-
methods.
456+
If you want to start a Trio task that shall be monitored by ``trio_asyncio``
457+
(i.e. an uncaught error will propagate to, and terminate, the asyncio event
458+
loop) instead of a :class:`asyncio.Future`, use
459+
:meth:`TrioEventLoop.run_trio_task`.
429460

430-
.. autodoc: trio_asyncio.wrap_trio_context
461+
.. autodoc: trio_asyncio.TrioEventLoop.run_trio_task
431462
432463
Multiple asyncio loops
433464
++++++++++++++++++++++

tests/interop/test_adapter.py

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
from trio_asyncio import aio_as_trio, allow_asyncio
2+
from trio_asyncio import aio_as_trio, trio_as_aio, allow_asyncio
33
from trio_asyncio import aio2trio, trio2aio
44
import asyncio
55
import trio
@@ -15,14 +15,29 @@ class SomeThing:
1515
def __init__(self, loop):
1616
self.loop = loop
1717

18-
@aio2trio
1918
async def dly_trio(self):
2019
if sys.version_info >= (3, 7):
2120
assert sniffio.current_async_library() == "trio"
2221
await trio.sleep(0.01)
2322
self.flag |= 2
2423
return 8
2524

25+
@trio_as_aio
26+
async def dly_trio_adapted(self):
27+
if sys.version_info >= (3, 7):
28+
assert sniffio.current_async_library() == "trio"
29+
await trio.sleep(0.01)
30+
self.flag |= 2
31+
return 8
32+
33+
@aio2trio
34+
async def dly_trio_depr(self):
35+
if sys.version_info >= (3, 7):
36+
assert sniffio.current_async_library() == "trio"
37+
await trio.sleep(0.01)
38+
self.flag |= 2
39+
return 8
40+
2641
@trio2aio
2742
async def dly_asyncio_depr(self):
2843
if sys.version_info >= (3, 7):
@@ -39,15 +54,15 @@ async def dly_asyncio_adapted(self):
3954
self.flag |= 1
4055
return 4
4156

42-
async def dly_asyncio(self):
43-
if sys.version_info >= (3, 7):
57+
async def dly_asyncio(self, do_test=True):
58+
if do_test and sys.version_info >= (3, 7):
4459
assert sniffio.current_async_library() == "asyncio"
4560
await asyncio.sleep(0.01, loop=self.loop)
4661
self.flag |= 1
4762
return 4
4863

49-
async def iter_asyncio(self):
50-
if sys.version_info >= (3, 7):
64+
async def iter_asyncio(self, do_test=True):
65+
if do_test and sys.version_info >= (3, 7):
5166
assert sniffio.current_async_library() == "asyncio"
5267
await asyncio.sleep(0.01, loop=self.loop)
5368
yield 1
@@ -56,6 +71,16 @@ async def iter_asyncio(self):
5671
await asyncio.sleep(0.01, loop=self.loop)
5772
self.flag |= 1
5873

74+
async def iter_trio(self):
75+
if sys.version_info >= (3, 7):
76+
assert sniffio.current_async_library() == "trio"
77+
await trio.sleep(0.01)
78+
yield 1
79+
await trio.sleep(0.01)
80+
yield 2
81+
await trio.sleep(0.01)
82+
self.flag |= 1
83+
5984
@asynccontextmanager
6085
async def ctx_asyncio(self):
6186
await asyncio.sleep(0.01, loop=self.loop)
@@ -64,13 +89,30 @@ async def ctx_asyncio(self):
6489
await asyncio.sleep(0.01, loop=self.loop)
6590
self.flag |= 2
6691

92+
@asynccontextmanager
93+
async def ctx_trio(self):
94+
await trio.sleep(0.01)
95+
self.flag |= 1
96+
yield self
97+
await trio.sleep(0.01)
98+
self.flag |= 2
99+
67100
class TestAdapt(aiotest.TestCase):
68101
@pytest.mark.trio
69-
async def test_asyncio_trio(self, loop):
102+
async def test_asyncio_trio_depr(self, loop):
103+
"""Call asyncio from trio"""
104+
105+
sth = SomeThing(loop)
106+
res = await aio_as_trio(sth.dly_trio_depr, loop=loop)()
107+
assert res == 8
108+
assert sth.flag == 2
109+
110+
@pytest.mark.trio
111+
async def test_asyncio_trio_adapted(self, loop):
70112
"""Call asyncio from trio"""
71113

72114
sth = SomeThing(loop)
73-
res = await aio_as_trio(sth.dly_trio, loop=loop)()
115+
res = await aio_as_trio(sth.dly_trio_adapted, loop=loop)()
74116
assert res == 8
75117
assert sth.flag == 2
76118

@@ -80,7 +122,7 @@ async def test_asyncio_trio_depr(self, loop):
80122

81123
sth = SomeThing(loop)
82124
with test_utils.deprecate(self):
83-
res = await loop.run_asyncio(sth.dly_trio)
125+
res = await loop.run_asyncio(sth.dly_trio_adapted)
84126
assert res == 8
85127
assert sth.flag == 2
86128

@@ -108,7 +150,7 @@ async def test_trio_asyncio_awaitable(self, loop):
108150
@pytest.mark.trio
109151
async def test_trio_asyncio_future(self, loop):
110152
sth = SomeThing(loop)
111-
f = sth.dly_asyncio()
153+
f = sth.dly_asyncio(do_test=False)
112154
f = asyncio.ensure_future(f)
113155
res = await aio_as_trio(f)
114156
assert res == 4
@@ -125,24 +167,65 @@ async def test_trio_asyncio_depr(self, loop):
125167
async def test_trio_asyncio_iter(self, loop):
126168
sth = SomeThing(loop)
127169
n = 0
170+
if sys.version_info >= (3, 7):
171+
assert sniffio.current_async_library() == "trio"
128172
async for x in aio_as_trio(sth.iter_asyncio()):
129173
n += 1
130174
assert x == n
131175
assert n == 2
132176
assert sth.flag == 1
133177

178+
async def run_asyncio_trio_iter(self, loop):
179+
sth = SomeThing(loop)
180+
n = 0
181+
if sys.version_info >= (3, 7):
182+
assert sniffio.current_async_library() == "asyncio"
183+
async for x in trio_as_aio(sth.iter_trio()):
184+
n += 1
185+
assert x == n
186+
assert n == 2
187+
assert sth.flag == 1
188+
189+
@pytest.mark.trio
190+
async def test_asyncio_trio_iter(self, loop):
191+
await aio_as_trio(self.run_asyncio_trio_iter)(loop)
192+
134193
@pytest.mark.trio
135194
async def test_trio_asyncio_ctx(self, loop):
136195
sth = SomeThing(loop)
137196
async with aio_as_trio(sth.ctx_asyncio()):
138197
assert sth.flag == 1
139198
assert sth.flag == 3
140199

200+
async def run_asyncio_trio_ctx(self, loop):
201+
sth = SomeThing(loop)
202+
async with trio_as_aio(sth.ctx_trio()):
203+
assert sth.flag == 1
204+
assert sth.flag == 3
205+
206+
@pytest.mark.trio
207+
async def test_asyncio_trio_ctx(self, loop):
208+
await aio_as_trio(self.run_asyncio_trio_ctx)(loop)
209+
141210
class TestAllow(aiotest.TestCase):
142211
async def run_asyncio_trio(self, loop):
143212
"""Call asyncio from trio"""
144213
sth = SomeThing(loop)
145-
res = await sth.dly_trio()
214+
res = await trio_as_aio(sth.dly_trio, loop=loop)()
215+
assert res == 8
216+
assert sth.flag == 2
217+
218+
async def run_asyncio_trio_adapted(self, loop):
219+
"""Call asyncio from trio"""
220+
sth = SomeThing(loop)
221+
res = await sth.dly_trio_adapted()
222+
assert res == 8
223+
assert sth.flag == 2
224+
225+
async def run_asyncio_trio_depr(self, loop):
226+
"""Call asyncio from trio"""
227+
sth = SomeThing(loop)
228+
res = await sth.dly_trio_depr()
146229
assert res == 8
147230
assert sth.flag == 2
148231

@@ -152,7 +235,7 @@ async def test_asyncio_trio(self, loop):
152235

153236
async def run_trio_asyncio(self, loop):
154237
sth = SomeThing(loop)
155-
res = await sth.dly_asyncio()
238+
res = await sth.dly_asyncio(do_test=False)
156239
assert res == 4
157240
assert sth.flag == 1
158241

@@ -162,7 +245,7 @@ async def test_trio_asyncio(self, loop):
162245

163246
async def run_trio_asyncio_future(self, loop):
164247
sth = SomeThing(loop)
165-
f = sth.dly_asyncio()
248+
f = sth.dly_asyncio(do_test=False)
166249
f = asyncio.ensure_future(f)
167250
res = await f
168251
assert res == 4
@@ -185,7 +268,7 @@ async def test_trio_asyncio_depr(self, loop):
185268
async def run_trio_asyncio_iter(self, loop):
186269
sth = SomeThing(loop)
187270
n = 0
188-
async for x in sth.iter_asyncio():
271+
async for x in sth.iter_asyncio(do_test=False):
189272
n += 1
190273
assert x == n
191274
assert n == 2

0 commit comments

Comments
 (0)