Show More
@@ -1,28 +1,35 b'' | |||||
1 | """ |
|
1 | """ | |
2 | Async helper function that are invalid syntax on Python 3.5 and below. |
|
2 | Async helper function that are invalid syntax on Python 3.5 and below. | |
3 |
|
3 | |||
4 | Known limitation and possible improvement. |
|
4 | This code is best effort, and may have edge cases not behaving as expected. In | |
|
5 | particular it contain a number of heuristics to detect whether code is | |||
|
6 | effectively async and need to run in an event loop or not. | |||
5 |
|
7 | |||
6 | Top level code that contain a return statement (instead of, or in addition to |
|
8 | Some constructs (like top-level `return`, or `yield`) are taken care of | |
7 | await) will be detected as requiring being wrapped in async calls. This should |
|
9 | explicitly to actually raise a SyntaxError and stay as close as possible to | |
8 | be prevented as early return will not work. |
|
10 | Python semantics. | |
9 | """ |
|
11 | """ | |
10 |
|
12 | |||
11 |
|
13 | |||
12 | import ast |
|
14 | import ast | |
13 | import sys |
|
15 | import sys | |
14 | import inspect |
|
|||
15 | from textwrap import dedent, indent |
|
16 | from textwrap import dedent, indent | |
16 | from types import CodeType |
|
|||
17 |
|
17 | |||
18 |
|
18 | |||
19 | def _asyncio_runner(coro): |
|
19 | class _AsyncIORunner: | |
20 | """ |
|
20 | ||
21 | Handler for asyncio autoawait |
|
21 | def __call__(self, coro): | |
22 |
|
|
22 | """ | |
23 | import asyncio |
|
23 | Handler for asyncio autoawait | |
|
24 | """ | |||
|
25 | import asyncio | |||
|
26 | ||||
|
27 | return asyncio.get_event_loop().run_until_complete(coro) | |||
24 |
|
28 | |||
25 | return asyncio.get_event_loop().run_until_complete(coro) |
|
29 | def __str__(self): | |
|
30 | return 'asyncio' | |||
|
31 | ||||
|
32 | _asyncio_runner = _AsyncIORunner() | |||
26 |
|
33 | |||
27 |
|
34 | |||
28 | def _curio_runner(coroutine): |
|
35 | def _curio_runner(coroutine): | |
@@ -36,12 +43,14 b' def _curio_runner(coroutine):' | |||||
36 |
|
43 | |||
37 | def _trio_runner(async_fn): |
|
44 | def _trio_runner(async_fn): | |
38 | import trio |
|
45 | import trio | |
|
46 | ||||
39 | async def loc(coro): |
|
47 | async def loc(coro): | |
40 | """ |
|
48 | """ | |
41 | We need the dummy no-op async def to protect from |
|
49 | We need the dummy no-op async def to protect from | |
42 | trio's internal. See https://github.com/python-trio/trio/issues/89 |
|
50 | trio's internal. See https://github.com/python-trio/trio/issues/89 | |
43 | """ |
|
51 | """ | |
44 | return await coro |
|
52 | return await coro | |
|
53 | ||||
45 | return trio.run(loc, async_fn) |
|
54 | return trio.run(loc, async_fn) | |
46 |
|
55 | |||
47 |
|
56 | |||
@@ -60,7 +69,9 b' def _pseudo_sync_runner(coro):' | |||||
60 | return exc.value |
|
69 | return exc.value | |
61 | else: |
|
70 | else: | |
62 | # TODO: do not raise but return an execution result with the right info. |
|
71 | # TODO: do not raise but return an execution result with the right info. | |
63 | raise RuntimeError("{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)) |
|
72 | raise RuntimeError( | |
|
73 | "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__) | |||
|
74 | ) | |||
64 |
|
75 | |||
65 |
|
76 | |||
66 | def _asyncify(code: str) -> str: |
|
77 | def _asyncify(code: str) -> str: | |
@@ -69,7 +80,7 b' def _asyncify(code: str) -> str:' | |||||
69 | And setup a bit of context to run it later. |
|
80 | And setup a bit of context to run it later. | |
70 | """ |
|
81 | """ | |
71 | res = dedent( |
|
82 | res = dedent( | |
72 | """ |
|
83 | """ | |
73 | async def __wrapper__(): |
|
84 | async def __wrapper__(): | |
74 | try: |
|
85 | try: | |
75 | {usercode} |
|
86 | {usercode} | |
@@ -86,12 +97,13 b' class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):' | |||||
86 | the implementation involves wrapping the repl in an async function, it |
|
97 | the implementation involves wrapping the repl in an async function, it | |
87 | is erroneously allowed (e.g. yield or return at the top level) |
|
98 | is erroneously allowed (e.g. yield or return at the top level) | |
88 | """ |
|
99 | """ | |
|
100 | ||||
89 | def generic_visit(self, node): |
|
101 | def generic_visit(self, node): | |
90 | func_types = (ast.FunctionDef, ast.AsyncFunctionDef) |
|
102 | func_types = (ast.FunctionDef, ast.AsyncFunctionDef) | |
91 | invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) |
|
103 | invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) | |
92 |
|
104 | |||
93 | if isinstance(node, func_types): |
|
105 | if isinstance(node, func_types): | |
94 |
return |
|
106 | return # Don't recurse into functions | |
95 | elif isinstance(node, invalid_types): |
|
107 | elif isinstance(node, invalid_types): | |
96 | raise SyntaxError() |
|
108 | raise SyntaxError() | |
97 | else: |
|
109 | else: |
@@ -2806,7 +2806,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
2806 | self.events.trigger('post_run_cell', result) |
|
2806 | self.events.trigger('post_run_cell', result) | |
2807 | return result |
|
2807 | return result | |
2808 |
|
2808 | |||
2809 | def _run_cell(self, raw_cell, store_history, silent, shell_futures): |
|
2809 | def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures:bool): | |
2810 | """Internal method to run a complete IPython cell.""" |
|
2810 | """Internal method to run a complete IPython cell.""" | |
2811 | coro = self.run_cell_async( |
|
2811 | coro = self.run_cell_async( | |
2812 | raw_cell, |
|
2812 | raw_cell, | |
@@ -2815,11 +2815,16 b' class InteractiveShell(SingletonConfigurable):' | |||||
2815 | shell_futures=shell_futures, |
|
2815 | shell_futures=shell_futures, | |
2816 | ) |
|
2816 | ) | |
2817 |
|
2817 | |||
|
2818 | # run_cell_async is async, but may not actually need and eventloop. | |||
|
2819 | # when this is the case, we want to run it using the pseudo_sync_runner | |||
|
2820 | # so that code can invoke eventloops (for example via the %run , and | |||
|
2821 | # `%paste` magic. | |||
2818 | try: |
|
2822 | try: | |
2819 | interactivity = coro.send(None) |
|
2823 | interactivity = coro.send(None) | |
2820 | except StopIteration as exc: |
|
2824 | except StopIteration as exc: | |
2821 | return exc.value |
|
2825 | return exc.value | |
2822 |
|
2826 | |||
|
2827 | # if code was not async, sending `None` was actually executing the code. | |||
2823 | if isinstance(interactivity, ExecutionResult): |
|
2828 | if isinstance(interactivity, ExecutionResult): | |
2824 | return interactivity |
|
2829 | return interactivity | |
2825 |
|
2830 | |||
@@ -3159,7 +3164,6 b' class InteractiveShell(SingletonConfigurable):' | |||||
3159 |
|
3164 | |||
3160 | return eval(code_obj, user_ns) |
|
3165 | return eval(code_obj, user_ns) | |
3161 |
|
3166 | |||
3162 | @asyncio.coroutine |
|
|||
3163 | def run_code(self, code_obj, result=None, *, async_=False): |
|
3167 | def run_code(self, code_obj, result=None, *, async_=False): | |
3164 | """Execute a code object. |
|
3168 | """Execute a code object. | |
3165 |
|
3169 |
@@ -632,7 +632,12 b' class AsyncMagics(BasicMagics):' | |||||
632 | runner, and activate autoawait. |
|
632 | runner, and activate autoawait. | |
633 |
|
633 | |||
634 | If the object is a fully qualified object name, attempt to import it and |
|
634 | If the object is a fully qualified object name, attempt to import it and | |
635 |
set it as the runner, and activate autoawait. |
|
635 | set it as the runner, and activate autoawait. | |
|
636 | ||||
|
637 | ||||
|
638 | The exact behavior of autoawait is experimental and subject to change | |||
|
639 | across version of IPython and Python. | |||
|
640 | """ | |||
636 |
|
641 | |||
637 | param = parameter_s.strip() |
|
642 | param = parameter_s.strip() | |
638 | d = {True: "on", False: "off"} |
|
643 | d = {True: "on", False: "off"} |
@@ -19,23 +19,6 b' from IPython.terminal.ipapp import load_default_config' | |||||
19 | from traitlets import Bool, CBool, Unicode |
|
19 | from traitlets import Bool, CBool, Unicode | |
20 | from IPython.utils.io import ask_yes_no |
|
20 | from IPython.utils.io import ask_yes_no | |
21 |
|
21 | |||
22 | from contextlib import contextmanager |
|
|||
23 |
|
||||
24 | _sentinel = object() |
|
|||
25 | @contextmanager |
|
|||
26 | def new_context(): |
|
|||
27 | import trio._core._run as tcr |
|
|||
28 | old_runner = getattr(tcr.GLOBAL_RUN_CONTEXT, 'runner', _sentinel) |
|
|||
29 | old_task = getattr(tcr.GLOBAL_RUN_CONTEXT, 'task', None) |
|
|||
30 | if old_runner is not _sentinel: |
|
|||
31 | del tcr.GLOBAL_RUN_CONTEXT.runner |
|
|||
32 | tcr.GLOBAL_RUN_CONTEXT.task = None |
|
|||
33 | yield |
|
|||
34 | if old_runner is not _sentinel: |
|
|||
35 | tcr.GLOBAL_RUN_CONTEXT.runner = old_runner |
|
|||
36 | tcr.GLOBAL_RUN_CONTEXT.task = old_task |
|
|||
37 |
|
||||
38 |
|
||||
39 | class KillEmbedded(Exception):pass |
|
22 | class KillEmbedded(Exception):pass | |
40 |
|
23 | |||
41 | # kept for backward compatibility as IPython 6 was released with |
|
24 | # kept for backward compatibility as IPython 6 was released with | |
@@ -400,12 +383,11 b' def embed(**kwargs):' | |||||
400 | cls = type(saved_shell_instance) |
|
383 | cls = type(saved_shell_instance) | |
401 | cls.clear_instance() |
|
384 | cls.clear_instance() | |
402 | frame = sys._getframe(1) |
|
385 | frame = sys._getframe(1) | |
403 | with new_context(): |
|
386 | shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( | |
404 | shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( |
|
387 | frame.f_code.co_filename, frame.f_lineno), **kwargs) | |
405 | frame.f_code.co_filename, frame.f_lineno), **kwargs) |
|
388 | shell(header=header, stack_depth=2, compile_flags=compile_flags, | |
406 | shell(header=header, stack_depth=2, compile_flags=compile_flags, |
|
389 | _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) | |
407 | _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) |
|
390 | InteractiveShellEmbed.clear_instance() | |
408 | InteractiveShellEmbed.clear_instance() |
|
|||
409 | #restore previous instance |
|
391 | #restore previous instance | |
410 | if saved_shell_instance is not None: |
|
392 | if saved_shell_instance is not None: | |
411 | cls = type(saved_shell_instance) |
|
393 | cls = type(saved_shell_instance) |
@@ -3,6 +3,11 b'' | |||||
3 | Asynchronous in REPL: Autoawait |
|
3 | Asynchronous in REPL: Autoawait | |
4 | =============================== |
|
4 | =============================== | |
5 |
|
5 | |||
|
6 | .. note:: | |||
|
7 | ||||
|
8 | This feature is experimental and behavior can change betwen python and | |||
|
9 | IPython version without prior deprecation. | |||
|
10 | ||||
6 | Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the |
|
11 | Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the | |
7 | ability to run asynchronous code from the REPL. Constructs which are |
|
12 | ability to run asynchronous code from the REPL. Constructs which are | |
8 | :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. |
|
13 | :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython. | |
@@ -12,10 +17,10 b' notebook interface or any other frontend using the Jupyter protocol will need to' | |||||
12 | use a newer version of IPykernel. The details of how async code runs in |
|
17 | use a newer version of IPykernel. The details of how async code runs in | |
13 | IPykernel will differ between IPython, IPykernel and their versions. |
|
18 | IPykernel will differ between IPython, IPykernel and their versions. | |
14 |
|
19 | |||
15 |
When a supported library is used, IPython will automatically |
|
20 | When a supported library is used, IPython will automatically allow Futures and | |
16 |
Coroutines in the REPL. This will happen if an :ref:`await |
|
21 | Coroutines in the REPL to be ``await`` ed. This will happen if an :ref:`await | |
17 |
other async constructs like async-with, async-for) is use at |
|
22 | <await>` (or any other async constructs like async-with, async-for) is use at | |
18 | if any structure valid only in `async def |
|
23 | top level scope, or if any structure valid only in `async def | |
19 | <https://docs.python.org/3/reference/compound_stmts.html#async-def>`_ function |
|
24 | <https://docs.python.org/3/reference/compound_stmts.html#async-def>`_ function | |
20 | context are present. For example, the following being a syntax error in the |
|
25 | context are present. For example, the following being a syntax error in the | |
21 | Python REPL:: |
|
26 | Python REPL:: | |
@@ -58,15 +63,13 b' use the :magic:`%autoawait` magic to toggle the behavior at runtime::' | |||||
58 | In [1]: %autoawait False |
|
63 | In [1]: %autoawait False | |
59 |
|
64 | |||
60 | In [2]: %autoawait |
|
65 | In [2]: %autoawait | |
61 |
IPython autoawait is `Off`, and set to use ` |
|
66 | IPython autoawait is `Off`, and set to use `asyncio` | |
62 |
|
67 | |||
63 |
|
68 | |||
64 |
|
69 | |||
65 | By default IPython will assume integration with Python's provided |
|
70 | By default IPython will assume integration with Python's provided | |
66 | :mod:`asyncio`, but integration with other libraries is provided. In particular |
|
71 | :mod:`asyncio`, but integration with other libraries is provided. In particular | |
67 |
we provide experimental integration with the ``curio`` and ``trio`` library |
|
72 | we provide experimental integration with the ``curio`` and ``trio`` library. | |
68 | later one being necessary if you require the ability to do nested call of |
|
|||
69 | IPython's ``embed()`` functionality. |
|
|||
70 |
|
73 | |||
71 | You can switch current integration by using the |
|
74 | You can switch current integration by using the | |
72 | ``c.InteractiveShell.loop_runner`` option or the ``autoawait <name |
|
75 | ``c.InteractiveShell.loop_runner`` option or the ``autoawait <name | |
@@ -111,7 +114,7 b' other features of IPython and various registered extensions. In particular if yo' | |||||
111 | are a direct or indirect user of the AST transformers, these may not apply to |
|
114 | are a direct or indirect user of the AST transformers, these may not apply to | |
112 | your code. |
|
115 | your code. | |
113 |
|
116 | |||
114 |
When |
|
117 | When using command line IPython, the default loop (or runner) does not process | |
115 | in the background, so top level asynchronous code must finish for the REPL to |
|
118 | in the background, so top level asynchronous code must finish for the REPL to | |
116 | allow you to enter more code. As with usual Python semantic, the awaitables are |
|
119 | allow you to enter more code. As with usual Python semantic, the awaitables are | |
117 | started only when awaited for the first time. That is to say, in first example, |
|
120 | started only when awaited for the first time. That is to say, in first example, | |
@@ -122,25 +125,20 b' Effects on IPython.embed()' | |||||
122 | ========================== |
|
125 | ========================== | |
123 |
|
126 | |||
124 | IPython core being asynchronous, the use of ``IPython.embed()`` will now require |
|
127 | IPython core being asynchronous, the use of ``IPython.embed()`` will now require | |
125 | a loop to run. In order to allow ``IPython.embed()`` to be nested, as most event |
|
128 | a loop to run. By default IPython will use a fake coroutine runner which should | |
126 | loops can't be nested, ``IPython.embed()`` default to a pseudo-synchronous mode, |
|
129 | allow ``IPython.embed()`` to be nested. Though this will prevent usage of the | |
127 | where async code is not allowed. This mode is available in classical IPython |
|
130 | ``autoawait`` feature when using IPython embed. | |
128 | using ``%autoawait sync`` |
|
|||
129 |
|
||||
130 |
|
||||
131 |
|
||||
132 | This affect the ability to nest ``IPython.embed()`` which may |
|
|||
133 | require you to install alternate IO libraries like ``curio`` and ``trio`` |
|
|||
134 |
|
||||
135 |
|
131 | |||
|
132 | You can set explicitly a coroutine runner for ``embed()`` if you desire to run | |||
|
133 | asynchronous code, the exact behavior is though undefined. | |||
136 |
|
134 | |||
137 | Internals |
|
135 | Internals | |
138 | ========= |
|
136 | ========= | |
139 |
|
137 | |||
140 | As running asynchronous code is not supported in interactive REPL (as of Python |
|
138 | As running asynchronous code is not supported in interactive REPL (as of Python | |
141 |
3.7) we have to rely to a number of complex workaround |
|
139 | 3.7) we have to rely to a number of complex workaround and heuristic to allow | |
142 |
It is interesting to understand how this works in order to |
|
140 | this to happen. It is interesting to understand how this works in order to | |
143 | bugs, or provide a custom runner. |
|
141 | comprehend potential bugs, or provide a custom runner. | |
144 |
|
142 | |||
145 | Among the many approaches that are at our disposition, we find only one that |
|
143 | Among the many approaches that are at our disposition, we find only one that | |
146 | suited out need. Under the hood we use the code object from a async-def function |
|
144 | suited out need. Under the hood we use the code object from a async-def function | |
@@ -168,8 +166,10 b' On top of the above there are significant modification to the AST of' | |||||
168 | significant overhead to this kind of code. |
|
166 | significant overhead to this kind of code. | |
169 |
|
167 | |||
170 | By default the generated coroutine function will be consumed by Asyncio's |
|
168 | By default the generated coroutine function will be consumed by Asyncio's | |
171 |
``loop_runner = asyncio.get_evenloop().run_until_complete()`` method |
|
169 | ``loop_runner = asyncio.get_evenloop().run_until_complete()`` method if | |
172 | though possible to provide your own. |
|
170 | ``async`` mode is deemed necessary, otherwise the coroutine will just be | |
|
171 | exhausted in a simple runner. It is though possible to change the default | |||
|
172 | runner. | |||
173 |
|
173 | |||
174 | A loop runner is a *synchronous* function responsible from running a coroutine |
|
174 | A loop runner is a *synchronous* function responsible from running a coroutine | |
175 | object. |
|
175 | object. | |
@@ -208,3 +208,6 b' We can set it up by passing it to ``%autoawait``::' | |||||
208 | Asynchronous programming in python (and in particular in the REPL) is still a |
|
208 | Asynchronous programming in python (and in particular in the REPL) is still a | |
209 | relatively young subject. We expect some code to not behave as you expect, so |
|
209 | relatively young subject. We expect some code to not behave as you expect, so | |
210 | feel free to contribute improvements to this codebase and give us feedback. |
|
210 | feel free to contribute improvements to this codebase and give us feedback. | |
|
211 | ||||
|
212 | We invite you to thoroughly test this feature and report any unexpected behavior | |||
|
213 | as well as propose any improvement. |
@@ -23,6 +23,10 b' yourself. To know more read the :ref:`autoawait` section of our docs, see' | |||||
23 | ... |
|
23 | ... | |
24 | } |
|
24 | } | |
25 |
|
25 | |||
|
26 | .. note:: | |||
|
27 | ||||
|
28 | Async integration is experimental code, behavior may change or be removed | |||
|
29 | between Python and IPython versions without warnings. | |||
26 |
|
30 | |||
27 | Integration is by default with `asyncio`, but other libraries can be configured, |
|
31 | Integration is by default with `asyncio`, but other libraries can be configured, | |
28 | like ``curio`` or ``trio``, to improve concurrency in the REPL:: |
|
32 | like ``curio`` or ``trio``, to improve concurrency in the REPL:: | |
@@ -57,13 +61,33 b' See :ref:`autoawait` for more information.' | |||||
57 | Asynchronous code in a Notebook interface or any other frontend using the |
|
61 | Asynchronous code in a Notebook interface or any other frontend using the | |
58 | Jupyter Protocol will need further updates of the IPykernel package. |
|
62 | Jupyter Protocol will need further updates of the IPykernel package. | |
59 |
|
63 | |||
|
64 | Non-Asynchronous code | |||
|
65 | --------------------- | |||
|
66 | ||||
|
67 | As the internal API of IPython are now asynchronous, IPython need to run under | |||
|
68 | an even loop. In order to allow many workflow, (like using the ``%run`` magic, | |||
|
69 | or copy_pasting code that explicitly starts/stop event loop), when top-level code | |||
|
70 | is detected as not being asynchronous, IPython code is advanced via a | |||
|
71 | pseudo-synchronous runner, and will not may not advance pending tasks. | |||
60 |
|
72 | |||
61 | Change to Nested Embed |
|
73 | Change to Nested Embed | |
62 | ---------------------- |
|
74 | ---------------------- | |
63 |
|
75 | |||
64 |
The introduction of the ability to run async code had |
|
76 | The introduction of the ability to run async code had some effect on the | |
65 | ability to use nested IPython. You may need to install the ``trio`` library |
|
77 | ``IPython.embed()`` API. By default embed will not allow you to run asynchronous | |
66 | (version 0.5 at the time of this writing) to |
|
78 | code unless a event loop is specified. | |
67 | have this feature working. |
|
79 | ||
|
80 | Expected Future changes | |||
|
81 | ----------------------- | |||
|
82 | ||||
|
83 | We expect more internal but public IPython function to become ``async``, and | |||
|
84 | will likely end up having a persisting event loop while IPython is running. | |||
68 |
|
85 | |||
|
86 | Thanks | |||
|
87 | ------ | |||
69 |
|
88 | |||
|
89 | This took more than a year in the making, and the code was rebased a number of | |||
|
90 | time leading to commit authorship that may have been lost in the final | |||
|
91 | Pull-Request. Huge thanks to many people for contribution, discussion, code, | |||
|
92 | documentation, use-case: dalejung, danielballan, ellisonbg, fperez, gnestor, | |||
|
93 | minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many other. |
General Comments 0
You need to be logged in to leave comments.
Login now