##// END OF EJS Templates
docs cleanup, reformat code, remove dead code.
Matthias Bussonnier -
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 # Don't recurse into functions
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 `await` Futures and
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 <await>` (or any
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 top level scope, or
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 `IPython.core.interactiveshell._asyncio_runner`
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, the
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 Using command line IPython, the default loop (or runner) does not process
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 to allow this to happen.
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 comprehend potential
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. It is
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 ripple effect on the
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