##// END OF EJS Templates
add notes some magic don't work yet
Matthias Bussonnier -
Show More
@@ -1,213 +1,222 b''
1 1 .. _autoawait:
2 2
3 3 Asynchronous in REPL: Autoawait
4 4 ===============================
5 5
6 6 .. note::
7 7
8 8 This feature is experimental and behavior can change betwen python and
9 9 IPython version without prior deprecation.
10 10
11 11 Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the
12 12 ability to run asynchronous code from the REPL. Constructs which are
13 13 :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython.
14 14
15 15 The example given here are for terminal IPython, running async code in a
16 16 notebook interface or any other frontend using the Jupyter protocol will need to
17 17 use a newer version of IPykernel. The details of how async code runs in
18 18 IPykernel will differ between IPython, IPykernel and their versions.
19 19
20 20 When a supported library is used, IPython will automatically allow Futures and
21 21 Coroutines in the REPL to be ``await`` ed. This will happen if an :ref:`await
22 22 <await>` (or any other async constructs like async-with, async-for) is use at
23 23 top level scope, or if any structure valid only in `async def
24 24 <https://docs.python.org/3/reference/compound_stmts.html#async-def>`_ function
25 25 context are present. For example, the following being a syntax error in the
26 26 Python REPL::
27 27
28 28 Python 3.6.0
29 29 [GCC 4.2.1]
30 30 Type "help", "copyright", "credits" or "license" for more information.
31 31 >>> import aiohttp
32 32 >>> result = aiohttp.get('https://api.github.com')
33 33 >>> response = await result
34 34 File "<stdin>", line 1
35 35 response = await result
36 36 ^
37 37 SyntaxError: invalid syntax
38 38
39 39 Should behave as expected in the IPython REPL::
40 40
41 41 Python 3.6.0
42 42 Type 'copyright', 'credits' or 'license' for more information
43 43 IPython 7.0.0 -- An enhanced Interactive Python. Type '?' for help.
44 44
45 45 In [1]: import aiohttp
46 46 ...: result = aiohttp.get('https://api.github.com')
47 47
48 48 In [2]: response = await result
49 49 <pause for a few 100s ms>
50 50
51 51 In [3]: await response.json()
52 52 Out[3]:
53 53 {'authorizations_url': 'https://api.github.com/authorizations',
54 54 'code_search_url': 'https://api.github.com/search/code?q={query}...',
55 55 ...
56 56 }
57 57
58 58
59 59 You can use the ``c.InteractiveShell.autoawait`` configuration option and set it
60 60 to :any:`False` to deactivate automatic wrapping of asynchronous code. You can also
61 61 use the :magic:`%autoawait` magic to toggle the behavior at runtime::
62 62
63 63 In [1]: %autoawait False
64 64
65 65 In [2]: %autoawait
66 66 IPython autoawait is `Off`, and set to use `asyncio`
67 67
68 68
69 69
70 70 By default IPython will assume integration with Python's provided
71 71 :mod:`asyncio`, but integration with other libraries is provided. In particular
72 72 we provide experimental integration with the ``curio`` and ``trio`` library.
73 73
74 74 You can switch current integration by using the
75 75 ``c.InteractiveShell.loop_runner`` option or the ``autoawait <name
76 76 integration>`` magic.
77 77
78 78 For example::
79 79
80 80 In [1]: %autoawait trio
81 81
82 82 In [2]: import trio
83 83
84 84 In [3]: async def child(i):
85 85 ...: print(" child %s goes to sleep"%i)
86 86 ...: await trio.sleep(2)
87 87 ...: print(" child %s wakes up"%i)
88 88
89 89 In [4]: print('parent start')
90 90 ...: async with trio.open_nursery() as n:
91 91 ...: for i in range(5):
92 92 ...: n.spawn(child, i)
93 93 ...: print('parent end')
94 94 parent start
95 95 child 2 goes to sleep
96 96 child 0 goes to sleep
97 97 child 3 goes to sleep
98 98 child 1 goes to sleep
99 99 child 4 goes to sleep
100 100 <about 2 seconds pause>
101 101 child 2 wakes up
102 102 child 1 wakes up
103 103 child 0 wakes up
104 104 child 3 wakes up
105 105 child 4 wakes up
106 106 parent end
107 107
108 108
109 109 In the above example, ``async with`` at top level scope is a syntax error in
110 110 Python.
111 111
112 112 Using this mode can have unexpected consequences if used in interaction with
113 113 other features of IPython and various registered extensions. In particular if you
114 114 are a direct or indirect user of the AST transformers, these may not apply to
115 115 your code.
116 116
117 117 When using command line IPython, the default loop (or runner) does not process
118 118 in the background, so top level asynchronous code must finish for the REPL to
119 119 allow you to enter more code. As with usual Python semantic, the awaitables are
120 120 started only when awaited for the first time. That is to say, in first example,
121 121 no network request is done between ``In[1]`` and ``In[2]``.
122 122
123 123
124 124 Effects on IPython.embed()
125 125 ==========================
126 126
127 127 IPython core being asynchronous, the use of ``IPython.embed()`` will now require
128 128 a loop to run. By default IPython will use a fake coroutine runner which should
129 129 allow ``IPython.embed()`` to be nested. Though this will prevent usage of the
130 130 ``autoawait`` feature when using IPython embed.
131 131
132 132 You can set explicitly a coroutine runner for ``embed()`` if you desire to run
133 133 asynchronous code, the exact behavior is though undefined.
134 134
135 Effects on Magics
136 =================
137
138 A couple of magics (``%%timeit``, ``%timeit``, ``%%time``, ``%%prun``) have not
139 yet been updated to work with asynchronous code and will raise syntax errors
140 when trying to use top-level ``await``. We welcome any contribution to help fix
141 those, and extra cases we haven't caught yet. We hope for better support in Cor
142 Python for top-level Async code.
143
135 144 Internals
136 145 =========
137 146
138 147 As running asynchronous code is not supported in interactive REPL (as of Python
139 148 3.7) we have to rely to a number of complex workaround and heuristic to allow
140 149 this to happen. It is interesting to understand how this works in order to
141 150 comprehend potential bugs, or provide a custom runner.
142 151
143 152 Among the many approaches that are at our disposition, we find only one that
144 153 suited out need. Under the hood we use the code object from a async-def function
145 154 and run it in global namespace after modifying it to not create a new
146 155 ``locals()`` scope::
147 156
148 157 async def inner_async():
149 158 locals().update(**global_namespace)
150 159 #
151 160 # here is user code
152 161 #
153 162 return last_user_statement
154 163 codeobj = modify(inner_async.__code__)
155 164 coroutine = eval(codeobj, user_ns)
156 165 display(loop_runner(coroutine))
157 166
158 167
159 168
160 169 The first thing you'll notice is that unlike classical ``exec``, there is only
161 170 one namespace. Second, user code runs in a function scope, and not a module
162 171 scope.
163 172
164 173 On top of the above there are significant modification to the AST of
165 174 ``function``, and ``loop_runner`` can be arbitrary complex. So there is a
166 175 significant overhead to this kind of code.
167 176
168 177 By default the generated coroutine function will be consumed by Asyncio's
169 178 ``loop_runner = asyncio.get_evenloop().run_until_complete()`` method if
170 179 ``async`` mode is deemed necessary, otherwise the coroutine will just be
171 180 exhausted in a simple runner. It is though possible to change the default
172 181 runner.
173 182
174 183 A loop runner is a *synchronous* function responsible from running a coroutine
175 184 object.
176 185
177 186 The runner is responsible from ensuring that ``coroutine`` run to completion,
178 187 and should return the result of executing the coroutine. Let's write a
179 188 runner for ``trio`` that print a message when used as an exercise, ``trio`` is
180 189 special as it usually prefer to run a function object and make a coroutine by
181 190 itself, we can get around this limitation by wrapping it in an async-def without
182 191 parameters and passing this value to ``trio``::
183 192
184 193
185 194 In [1]: import trio
186 195 ...: from types import CoroutineType
187 196 ...:
188 197 ...: def trio_runner(coro:CoroutineType):
189 198 ...: print('running asynchronous code')
190 199 ...: async def corowrap(coro):
191 200 ...: return await coro
192 201 ...: return trio.run(corowrap, coro)
193 202
194 203 We can set it up by passing it to ``%autoawait``::
195 204
196 205 In [2]: %autoawait trio_runner
197 206
198 207 In [3]: async def async_hello(name):
199 208 ...: await trio.sleep(1)
200 209 ...: print(f'Hello {name} world !')
201 210 ...: await trio.sleep(1)
202 211
203 212 In [4]: await async_hello('async')
204 213 running asynchronous code
205 214 Hello async world !
206 215
207 216
208 217 Asynchronous programming in python (and in particular in the REPL) is still a
209 218 relatively young subject. We expect some code to not behave as you expect, so
210 219 feel free to contribute improvements to this codebase and give us feedback.
211 220
212 221 We invite you to thoroughly test this feature and report any unexpected behavior
213 222 as well as propose any improvement.
@@ -1,93 +1,99 b''
1 1 Autowait: Asynchronous REPL
2 2 ---------------------------
3 3
4 4 Staring with IPython 7.0 and on Python 3.6+, IPython can automatically await
5 5 code at top level, you should not need to access an event loop or runner
6 6 yourself. To know more read the :ref:`autoawait` section of our docs, see
7 7 :ghpull:`11265` or try the following code::
8 8
9 9 Python 3.6.0
10 10 Type 'copyright', 'credits' or 'license' for more information
11 11 IPython 7.0.0 -- An enhanced Interactive Python. Type '?' for help.
12 12
13 13 In [1]: import aiohttp
14 14 ...: result = aiohttp.get('https://api.github.com')
15 15
16 16 In [2]: response = await result
17 17 <pause for a few 100s ms>
18 18
19 19 In [3]: await response.json()
20 20 Out[3]:
21 21 {'authorizations_url': 'https://api.github.com/authorizations',
22 22 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}',
23 23 ...
24 24 }
25 25
26 26 .. note::
27 27
28 28 Async integration is experimental code, behavior may change or be removed
29 29 between Python and IPython versions without warnings.
30 30
31 31 Integration is by default with `asyncio`, but other libraries can be configured,
32 32 like ``curio`` or ``trio``, to improve concurrency in the REPL::
33 33
34 34 In [1]: %autoawait trio
35 35
36 36 In [2]: import trio
37 37
38 38 In [3]: async def child(i):
39 39 ...: print(" child %s goes to sleep"%i)
40 40 ...: await trio.sleep(2)
41 41 ...: print(" child %s wakes up"%i)
42 42
43 43 In [4]: print('parent start')
44 44 ...: async with trio.open_nursery() as n:
45 45 ...: for i in range(3):
46 46 ...: n.spawn(child, i)
47 47 ...: print('parent end')
48 48 parent start
49 49 child 2 goes to sleep
50 50 child 0 goes to sleep
51 51 child 1 goes to sleep
52 52 <about 2 seconds pause>
53 53 child 2 wakes up
54 54 child 1 wakes up
55 55 child 0 wakes up
56 56 parent end
57 57
58 58 See :ref:`autoawait` for more information.
59 59
60 60
61 61 Asynchronous code in a Notebook interface or any other frontend using the
62 62 Jupyter Protocol will need further updates of the IPykernel package.
63 63
64 64 Non-Asynchronous code
65 65 ---------------------
66 66
67 67 As the internal API of IPython are now asynchronous, IPython need to run under
68 68 an even loop. In order to allow many workflow, (like using the ``%run`` magic,
69 69 or copy_pasting code that explicitly starts/stop event loop), when top-level code
70 70 is detected as not being asynchronous, IPython code is advanced via a
71 71 pseudo-synchronous runner, and will not may not advance pending tasks.
72 72
73 73 Change to Nested Embed
74 74 ----------------------
75 75
76 76 The introduction of the ability to run async code had some effect on the
77 77 ``IPython.embed()`` API. By default embed will not allow you to run asynchronous
78 78 code unless a event loop is specified.
79 79
80 Effects on Magics
81 -----------------
82
83 Some magics will not work with Async, and will need updates. Contribution
84 welcome.
85
80 86 Expected Future changes
81 87 -----------------------
82 88
83 89 We expect more internal but public IPython function to become ``async``, and
84 90 will likely end up having a persisting event loop while IPython is running.
85 91
86 92 Thanks
87 93 ------
88 94
89 95 This took more than a year in the making, and the code was rebased a number of
90 96 time leading to commit authorship that may have been lost in the final
91 97 Pull-Request. Huge thanks to many people for contribution, discussion, code,
92 98 documentation, use-case: dalejung, danielballan, ellisonbg, fperez, gnestor,
93 99 minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many other.
General Comments 0
You need to be logged in to leave comments. Login now