Skip to content

Commit a0a29da

Browse files
committed
Update documentation, add tests from it
1 parent b05403c commit a0a29da

3 files changed

Lines changed: 276 additions & 64 deletions

File tree

docs/source/usage.rst

Lines changed: 135 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -204,96 +204,172 @@ for :mod:`contextvars` support in :mod:`asyncio`.
204204
Cross-calling
205205
---------------
206206

207-
Calling asyncio from Trio
208-
+++++++++++++++++++++++++
207+
First, a bit of background.
209208

210-
Pass the function and any arguments to ``loop.run_asyncio()``. This method
211-
conforms to Trio's standard task semantics.
209+
For historical reasons, running an async function (of any
210+
flavor) with Python is a two-step process – that is, given ::
212211

213-
:func:`trio_asyncio.run_asyncio` is a shortcut for
214-
``asyncio.get_event_loop().run_asyncio``.
212+
async def proc():
213+
whatever()
215214

216-
::
215+
a call to ``await proc()`` does two things:
217216

218-
async def some_asyncio_code(foo):
219-
await asyncio.sleep(1)
220-
return foo*20
221-
222-
res = await trio_asyncio.run_asyncio(some_asyncio_code, 21)
223-
assert res == 420
217+
* ``proc()`` creates an ``awaitable``, i.e. something that has an
218+
``__await__`` method.
224219

225-
.. autodoc: trio_asyncio.run_asyncio
220+
* ``await proc()`` thus iterates this awaitable until it ends,
221+
supported by your event loop's runtime system.
226222

227-
If you already have a coroutine you need to await, call ``loop.run_coroutine()``.
223+
:mod:`asyncio` traditionally uses awaitables for indirect procedure calls,
224+
so you often see the pattern::
228225

229-
:func:`trio_asyncio.run_coroutine` is a shortcut for
230-
``asyncio.get_event_loop().run_coroutine``.
226+
async def some_code():
227+
pass
228+
async def run(proc):
229+
await proc
230+
await run(some_code())
231231

232-
::
232+
This method has a problem: it decouples creating the awailable from running
233+
it. If you decide to add code to ``run`` that retries running ``proc`` when
234+
it encounters a specific error, you're out of luck.
233235

234-
async def some_asyncio_code(foo):
235-
await asyncio.sleep(1)
236-
return foo*20
237-
238-
fut = asyncio.ensure_future(some_asyncio_code(21))
239-
res = await trio.run_coroutine(fut)
240-
assert res == 420
236+
Trio, in contrast, uses (async) callables::
241237

242-
.. autodoc: trio_asyncio.run_coroutine
238+
async def some_code():
239+
pass
240+
async def run(proc):
241+
await proc()
242+
await run(some_code)
243243

244-
You can also use the ``trio2aio`` function decorator::
244+
Here, calling ``proc`` multiple times is not a problem.
245245

246-
@trio2aio
247-
async def some_asyncio_code(self, foo):
248-
await asyncio.sleep(1)
249-
return foo+33
246+
:mod:`trio_asyncio` adheres to Trio conventions, but the
247+
:mod:`asyncio` way is also supported when possible.
250248

251-
# then, within a trio function
252-
res = await some_asyncio_code(9)
253-
assert res == 42
249+
Calling asyncio from Trio
250+
+++++++++++++++++++++++++
254251

255-
.. autodoc: trio_asyncio.trio2aio
252+
Wrap the callable, awaitable, generator, or iterator in :func:`aio_as_trio`.
256253

257-
If you already have a future, you can wait for it directly.
254+
Thus, you can call an :mod:`asyncio` function from :mod:`trio` thus::
258255

259-
.. autodoc: trio_asyncio.run_future
256+
async def aio_sleep(sec=1):
257+
await asyncio.sleep(sec)
258+
async def trio_sleep(sec=2):
259+
await aio_as_trio(aio_sleep)(sec)
260+
trio_asyncio.run(trio_sleep)(3)
260261

261-
:func:`trio_asyncio.run_future` does not require a running trio-asyncio
262-
main loop.
262+
or pre-wrapped::
263263

264-
If you need to call an asyncio-ish async context manager from Trio,
265-
``loop.run_asyncio()`` also works::
264+
@aio_as_trio
265+
async def aio_sleep(sec=1):
266+
await asyncio.sleep(sec)
267+
async def trio_sleep(sec=2):
268+
await aio_sleep(sec)
269+
trio_asyncio.run(trio_sleep, 3)
266270

267-
async with loop.run_asyncio(generate_context()) as ctx:
268-
await loop.run_asyncio(ctx.do_whatever)
271+
or as an awaitable::
269272

270-
As you can see from this example, the context that's returned is a "native"
271-
asyncio context, so you still need to use ``run_asyncio()`` if you call its
272-
methods.
273+
async def aio_sleep(sec=1):
274+
await asyncio.sleep(sec)
275+
async def trio_sleep(sec=2):
276+
await aio_as_trio(aio_sleep(sec))
277+
trio_asyncio.run(trio_sleep, 3)
273278

274-
Wrapping an async iterator also works::
279+
This also works with :mod:`asyncio` Futures::
275280

276-
async def slow_nums():
281+
async def aio_sleep(sec=1):
282+
await asyncio.sleep(sec)
283+
return 42
284+
async def trio_sleep(sec=2):
285+
f = aio_sleep(1)
286+
f = asyncio.ensure_future(f)
287+
r = await aio_as_trio(f)
288+
assert r == 42
289+
trio_asyncio.run(trio_sleep, 3)
290+
291+
You can wrap context handlers::
292+
293+
class AsyncCtx:
294+
async def __aenter__(self):
295+
await asyncio.sleep(1)
296+
return self
297+
async def __aexit__(self, *tb):
298+
await asyncio.sleep(1)
299+
async def delay(self, sec=1):
300+
await asyncio.sleep(sec)
301+
async def trio_ctx():
302+
async with aio_as_trio(AsyncCtx()) as ctx:
303+
print("within")
304+
await aio_as_trio(ctx.delay)(2)
305+
trio_asyncio.run(trio_ctx)
306+
307+
As you can see, while :func:`aio_as_trio` trio-izes the actual context,
308+
it doesn't know about the context's methods. You still need to treat them
309+
as :mod:`asyncio` methods and wrap them appropriately when you call them.
310+
311+
Note that *creating* the async context handler is not itself an
312+
asynchronous process, i.e. ``AsyncCtx.__init__`` is a normal
313+
synchronous procedure. Only the actual context handlers (the ``__aenter__``
314+
and ``__aexit__`` methods) are asynchronous. This is why you need to wrap
315+
the context handler itself – unlike simple procedure calls, you cannot wrap
316+
the call for generating the context handler.
317+
318+
Thus, the following code **will not work**::
319+
320+
async def trio_ctx():
321+
async with aio_as_trio(AsyncCtx)() as ctx:
322+
print("within")
323+
324+
You can also wrap async generators or iterators::
325+
326+
async def aio_slow():
277327
n = 0
278328
while True:
279-
asyncio.sleep(1)
329+
await asyncio.sleep(n)
280330
yield n
281331
n += 1
332+
async def printer():
333+
async for n in aio_as_trio(aio_slow()):
334+
print(n)
335+
trio_asyncio.run(printer)
336+
337+
As above, *creating* the async iterator is not itself an asynchronous
338+
process, i.e. the ``__aiter__`` method that creates the generator or
339+
iterator is a normal synchronous procedure. Only the actual iteration
340+
step (the iterator's ``__anext__`` method) is asynchronous. Again, you need
341+
to wrap the iterator, not the code creating it – the following code
342+
**will not work**::
282343

283-
async def trio_code(loop):
284-
async for n in loop.run_asyncio(slow_nums()):
344+
async def printer():
345+
async for n in aio_as_trio(aio_slow)():
285346
print(n)
286347

287-
trio_asyncio.run(trio_code)
348+
.. autodoc: trio_asyncio.aio_as_trio
288349
289-
Note that in this case we're wrapping an async iterator, i.e. the object
290-
returned by calling ``slow_nums``, not the function itself.
291350
292351
Too complicated?
293-
++++++++++++++++
352+
----------------
353+
354+
There's also a somewhat-magic wrapper (:func:`allow_asyncio`) which,
355+
as the name implies, allows you to directly call :mod:`asyncio` functions.
356+
It also works for awaiting futures and for using generators or context
357+
managers::
358+
359+
async def hybrid():
360+
await trio.sleep(1)
361+
await asyncio.sleep(1)
362+
print("Well, that worked")
363+
trio_asyncio.run(trio_asyncio.allow_asyncio, hybrid)
364+
365+
This method works for one-off code. However, there are a couple of
366+
semantic differences between :mod:`asyncio` and :mod:`trio` which
367+
:func:`allow_asyncio` is unable to account for.
368+
369+
Worse, :func:`allow_asyncio` can be used to auto-adapt asyncio code to Trio
370+
callers, but not vice versa.
294371

295-
There's also a somewhat-magic wrapper which allows you to directly call
296-
:mod:`asyncio` functions from :mod:`trio`.
372+
Thus, you really should not use it for "real" programs or libraries.
297373

298374
.. autodoc: trio_asyncio.allow_asyncio
299375
@@ -387,10 +463,10 @@ Cancellations are also propagated whenever possible. This means
387463
* when the task started with ``run_trio()`` is cancelled,
388464
the future gets cancelled
389465

390-
* the future used in ``run_future()`` is cancelled when the Trio code
466+
* the future used in ``run_aio_future()`` is cancelled when the Trio code
391467
calling it is cancelled
392468

393-
* However, when the future passed to ``run_future()`` is cancelled (i.e.
469+
* However, when the future passed to ``run_aio_future()`` is cancelled (i.e.
394470
when the task associated with it raises ``asyncio.CancelledError``), that
395471
exception is passed along unchanged.
396472

0 commit comments

Comments
 (0)