Skip to content

Commit c7b9809

Browse files
authored
Merge pull request #69 from oremanj/deprecations
Clean up public API surface
2 parents fadc28e + 213e554 commit c7b9809

22 files changed

+1070
-810
lines changed

docs/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
]
3434

3535
autodoc_inherit_docstrings = False
36+
default_role = "obj"
3637

3738
# -- General configuration ------------------------------------------------
3839

docs/source/history.rst

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,27 @@ Release history
55

66
.. towncrier release notes start
77
8-
Trio_Asyncio 0.8.1 (2018-08-25)
8+
trio-asyncio 0.8.1 (2018-08-25)
99
-------------------------------
1010

1111
Features
1212
~~~~~~~~
1313

14-
- `trio_ayncio` now contains an `allow_asyncio` wrapper which allows you to
15-
seamlessly mix asyncio and trio semantics:: from trio_asyncio import run
16-
allow_asyncio async def main(): print("Sleeping 1") await asyncio.sleep(1)
17-
print("Sleeping 2") await trio.sleep(1) run(allow_asyncio, main) The problem
18-
with this solution is that Trio's cancellations will no longer be converted
19-
to asyncio's `CancelledError` within asyncio code. This may or may not be an
20-
issue for your code. (`#30
14+
- `trio_asyncio` now contains an `allow_asyncio` wrapper which allows you to
15+
seamlessly mix asyncio and trio semantics::
16+
17+
from trio_asyncio import run
18+
@allow_asyncio
19+
async def main():
20+
print("Sleeping 1")
21+
await asyncio.sleep(1)
22+
print("Sleeping 2")
23+
await trio.sleep(1)
24+
run(allow_asyncio, main)
25+
26+
The problem with this solution is that Trio's cancellations will no
27+
longer be converted to asyncio's `~asyncio.CancelledError` within asyncio
28+
code. This may or may not be an issue for your code. (`#30
2129
<https://github.com/python-trio/trio-asyncio/issues/30>`__)
2230

2331

docs/source/index.rst

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,50 @@
88
trio-asyncio: A re-implementation of the asyncio mainloop on top of Trio
99
========================================================================
1010

11-
Trio-Asyncio is *the* library of choice for a Python program that
12-
contains both `trio`_ and `asyncio`_ code.
11+
trio-asyncio is *the* library of choice for a Python program that
12+
contains both `Trio <https://trio.readthedocs.io/en/stable/>`__ and
13+
`asyncio` code.
1314

14-
.. _asyncio: https://docs.python.org/3/library/asyncio.html
15+
Trio has native concepts of tasks and task cancellation. Asyncio is based
16+
on callbacks and chaining Futures, albeit with nicer syntax, which make
17+
handling of failures and timeouts fundamentally less reliable, especially in
18+
larger programs. Thus, you really want to use Trio in your project.
19+
20+
On the other hand, there are quite a few robust libraries that have been
21+
implemented using asyncio, while Trio's ecosystem is relatively younger. You
22+
really don't want to re-invent any wheels in your project.
23+
24+
Thus, being able to use asyncio libraries from Trio is useful.
25+
trio-asyncio enables you to do that, and more.
1526

1627
With trio-asyncio, you can:
1728

18-
* incrementally convert your code to Trio. Start with a Trio mainloop, call
19-
your existing asyncio code, then successively convert procedures to Trio
20-
calling conventions.
29+
* Incrementally convert an asyncio application to Trio. Start with a
30+
Trio mainloop, call your existing asyncio code, then successively
31+
convert procedures to Trio calling conventions.
2132

22-
* use any asyncio-capable library.
33+
* Use any asyncio-capable library in a Trio application.
2334

24-
* use trio-asyncio as a building block for convincing other async-ish
35+
* Use trio-asyncio as a building block for convincing other async-ish
2536
libraries (Twisted, Promise, …) to be compatible with Trio.
2637

27-
Trio-Asyncio passes the test suite of some complex programs like
28-
``home-assistant``.
38+
trio-asyncio is tested against the complete asyncio test suite as
39+
shipped with each supported version of Python, although a small number of tests
40+
fail due to our limited support for starting and stopping the same event
41+
loop multiple times. It has also passed the test suite of some complex
42+
asyncio libraries such as ``home-assistant``.
2943

30-
In the past, the complete Python 3.6 test suite for asyncio and the tests
31-
for some well-known libraries like aiohttp also Just Work(ed). This is no
32-
longer the case because these tests rely heavily on stopping and restarting
33-
the ``asyncio`` event loop. ``trio_asyncio`` no longer supports this.
44+
.. note:: trio-asyncio is most useful for *applications*: it works best
45+
when you control the code that starts the event loop (such as
46+
the call to :func:`asyncio.run`). If you're writing a *library* and
47+
want to adopt a Trio-ish worldview without sacrificing asyncio
48+
compatibility, you might find `anyio <https://anyio.readthedocs.io/en/latest/>`__
49+
helpful.
3450

3551
Helpful facts:
3652

3753
* Supported environments: Linux, MacOS, or Windows running some kind of Python
38-
3.5.3-or-better (either CPython or PyPy3 is fine). \*BSD and illumus likely
54+
3.5.3-or-better (either CPython or PyPy3 is fine). \*BSD and illumOS likely
3955
work too, but are untested.
4056

4157
* Install: ``python3 -m pip install -U trio-asyncio`` (or on Windows, maybe
@@ -45,7 +61,7 @@ Helpful facts:
4561

4662
* Bug tracker and source code: https://github.com/python-trio/trio-asyncio
4763

48-
Inherited from `Trio <https://github.com/python-trio/trio>`_:
64+
Inherited from `Trio <https://github.com/python-trio/trio>`__:
4965

5066
* Real-time chat: https://gitter.im/python-trio/general
5167

@@ -57,10 +73,14 @@ Inherited from `Trio <https://github.com/python-trio/trio>`_:
5773
conduct <https://trio.readthedocs.io/en/latest/code-of-conduct.html>`_
5874
in all project spaces.
5975

76+
=====================
77+
trio-asyncio manual
78+
=====================
79+
6080
.. toctree::
6181
:maxdepth: 2
6282

63-
rationale.rst
83+
principles.rst
6484
usage.rst
6585
history.rst
6686

docs/source/principles.rst

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
.. currentmodule:: trio_asyncio
2+
3+
++++++++++++
4+
Principles
5+
++++++++++++
6+
7+
--------------------------------------
8+
Async function "flavors"
9+
--------------------------------------
10+
11+
As you might recall from the discussion of async "sandwiches" in the
12+
`Trio tutorial
13+
<https://trio.readthedocs.io/en/stable/tutorial.html#async-sandwich>`__,
14+
every async function ultimately must do its useful work by directly or
15+
indirectly calling back into the same async library (such as asyncio
16+
or Trio) that's managing the currently running event loop. If a
17+
function invoked within a :func:`trio.run` calls
18+
:func:`asyncio.sleep`, or a function invoked within an
19+
:func:`asyncio.run` calls :func:`trio.sleep`, the sleep function will
20+
send a message to the event loop that the event loop doesn't know how
21+
to handle, and some sort of error will result.
22+
23+
In a program that uses trio-asyncio, you probably have some async
24+
functions implemented in terms of Trio calls and some implemented in
25+
terms of asyncio calls. In order to keep track of which is which,
26+
we'll call these "Trio-flavored" and "asyncio-flavored" functions,
27+
respectively. It is critical that you understand which functions in
28+
your program are Trio-flavored and which are asyncio-flavored, just
29+
like it's critical that you understand which functions are synchronous
30+
and which ones are async. Unfortunately, there's no syntactic marker
31+
for flavor: both Trio-flavored and asyncio-flavored functions are
32+
defined with ``async def fn()`` and call other async functions with
33+
``await other_fn()``. You'll have to keep track of it some other
34+
way. To help you out, every function in trio-asyncio documents its
35+
flavor, and we recommend that you follow this convention in your own
36+
programs too.
37+
38+
The general rules that determine flavor are as follows:
39+
40+
* Every async function in the ``trio`` module is Trio-flavored.
41+
Every async function in the ``asyncio`` module is asyncio-flavored.
42+
43+
* Flavor is transitive: if async function ``foo()`` calls ``await
44+
bar()``, then ``foo()`` has ``bar()``'s flavor. (If ``foo()``
45+
calls ``await baz()`` too, then ``bar()`` and ``baz()`` had better
46+
have the same flavor.)
47+
48+
* trio-asyncio gives you the ability to call functions whose flavor is
49+
different than your own, but you must be explicit about it.
50+
:func:`trio_asyncio.aio_as_trio` takes an asyncio-flavored function
51+
and returns a Trio-flavored wrapper for it;
52+
:func:`trio_as_aio` takes a Trio-flavored function and
53+
returns an asyncio-flavored wrapper for it.
54+
55+
If you don't keep track of your function flavors correctly, you might
56+
get exceptions like the following:
57+
58+
* If you call a Trio function where an asyncio function is expected: ``RuntimeError:
59+
Task got bad yield:`` followed by either ``WaitTaskRescheduled(abort_func=...)``
60+
or ``<class 'trio._core._traps.CancelShieldedCheckpoint'>``
61+
62+
* If you call an asyncio function where a Trio function is expected: ``TypeError:
63+
trio.run received unrecognized yield message <Future ...>.``
64+
65+
Other errors are possible too.
66+
67+
-----------------------
68+
Flavor versus context
69+
-----------------------
70+
71+
The concept of function flavor is distinct from the concept of
72+
"asyncio context" or "Trio context". You're in Trio context if you're
73+
(indirectly) inside a call to :func:`trio.run`. You're in asyncio
74+
context if :func:`asyncio.get_running_loop` returns a valid event
75+
loop. In a trio-asyncio program, you will frequently be in both Trio
76+
context and asyncio context at the same time, but each async function is
77+
either Trio-flavored or asyncio-flavored (not both).
78+
79+
Most *synchronous* asyncio or Trio functions (:meth:`trio.Event.set`,
80+
:meth:`asyncio.StreamWriter.close`, etc) only require you to be in
81+
asyncio or Trio context, and work equally well regardless of the
82+
flavor of function calling them. The exceptions are functions that
83+
access the current task (:func:`asyncio.current_task`,
84+
:func:`trio.hazmat.current_task`, and anything that calls them),
85+
because there's only a meaningful concept of the current *foo* task
86+
when a *foo*-flavored function is executing. For example, this means
87+
context managers that set a timeout on their body (``with
88+
async_timeout.timeout(N):``, ``with trio.move_on_after(N):``) must be
89+
run from within the correct flavor of function.
90+
91+
---------------------------------
92+
Flavor transitions are explicit
93+
---------------------------------
94+
95+
As mentioned above, trio-asyncio does not generally allow you to
96+
transparently call ``await trio.something()`` from asyncio code, nor
97+
vice versa; you need to use :func:`aio_as_trio` or
98+
:func:`trio_as_aio` when calling a function whose flavor is
99+
different than yours. This is certainly more frustrating than having
100+
it "just work". Unfortunately, semantic differences between Trio and
101+
asyncio (such as how to signal cancellation) need to be resolved at
102+
each boundary between asyncio and Trio, and we haven't found a way to
103+
do this with acceptable performance and robustness unless those
104+
boundaries are marked.
105+
106+
If you insist on living on the wild side, trio-asyncio does provide
107+
:func:`allow_asyncio` which allows *limited,
108+
experimental, and slow* mixing of Trio-flavored and asyncio-flavored
109+
calls in the same Trio-flavored function.
110+
111+
-------------------------------------------
112+
trio-asyncio's place in the asyncio stack
113+
-------------------------------------------
114+
115+
At its base, asyncio doesn't know anything about futures or
116+
coroutines, nor does it have any concept of a task. All of these
117+
features are built on top of the simpler interfaces provided by the
118+
event loop. The event loop itself has little functionality beyond executing
119+
synchronous functions submitted with :meth:`~asyncio.loop.call_soon`
120+
and :meth:`~asyncio.loop.call_later`
121+
and invoking I/O availability callbacks registered using
122+
:meth:`~asyncio.loop.add_reader` and :meth:`~asyncio.loop.add_writer`
123+
at the appropriate times.
124+
125+
trio-asyncio provides an asyncio event loop implementation which
126+
performs these basic operations using Trio APIs. Everything else in
127+
asyncio (futures, tasks, cancellation, and so on) is ultimately
128+
implemented in terms of calls to event loop methods, and thus works
129+
"magically" with Trio once the trio-asyncio event loop is
130+
installed. This strategy provides a high level of compatibility with
131+
asyncio libraries, but it also means that asyncio-flavored code
132+
running under trio-asyncio doesn't benefit much from Trio's more
133+
structured approach to concurrent programming: cancellation,
134+
causality, and exception propagation in asyncio-flavored code are just
135+
as error-prone under trio-asyncio as they are under the default
136+
asyncio event loop. (Of course, your Trio-flavored code will still
137+
benefit from all the usual Trio guarantees.)
138+
139+
If you look at a Trio task tree, you'll see only one Trio task for the
140+
entire asyncio event loop. The distinctions between different asyncio
141+
tasks are erased, because they've all been merged into a single pot of
142+
callback soup by the time they get to trio-asyncio. Similarly, context
143+
variables will only work properly in asyncio-flavored code when
144+
running Python 3.7 or later (where they're supported natively), even
145+
though Trio supports them on earlier Pythons using a backport package.
146+
147+
----------------------------
148+
Event loop implementations
149+
----------------------------
150+
151+
An asyncio event loop may generally be interrupted and restarted at any
152+
time, simply by making repeated calls to :meth:`run_until_complete()
153+
<asyncio.loop.run_until_complete>`.
154+
Trio, however, requires one long-running main loop. trio-asyncio bridges
155+
this gap by providing two event loop implementations.
156+
157+
* The preferred option is to use an "async loop": inside a
158+
Trio-flavored async function, write ``async with
159+
trio_asyncio.open_loop() as loop:``. Within the ``async with``
160+
block (and anything it calls, and any tasks it starts, and so on),
161+
:func:`asyncio.get_event_loop` and :func:`asyncio.get_running_loop`
162+
will return *loop*. You can't manually start and stop an async
163+
loop. Instead, it starts when you enter the ``async with`` block and
164+
stops when you exit the block.
165+
166+
* The other option is a "sync loop".
167+
If you've imported trio-asyncio but aren't in Trio context, and you haven't
168+
installed a custom event loop policy, calling :func:`asyncio.new_event_loop`
169+
(including the implicit call made by the first :func:`asyncio.get_event_loop`
170+
in the main thread) will give you an event loop that transparently runs
171+
in a separate thread in order to support multiple
172+
calls to :meth:`~asyncio.loop.run_until_complete`,
173+
:meth:`~asyncio.loop.run_forever`, and :meth:`~asyncio.loop.stop`.
174+
Sync loops are intended to allow trio-asyncio to run the existing
175+
test suites of large asyncio libraries, which often call
176+
:meth:`~asyncio.loop.run_until_complete` on the same loop multiple times.
177+
Using them for other purposes is deprecated.

docs/source/rationale.rst

Lines changed: 0 additions & 66 deletions
This file was deleted.

0 commit comments

Comments
 (0)