Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit effc242

Browse files
committed
docs cleanup, reformat code, remove dead code.
1 parent 342427e commit effc242

6 files changed

Lines changed: 99 additions & 69 deletions

File tree

IPython/core/async_helpers.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
11
"""
22
Async helper function that are invalid syntax on Python 3.5 and below.
33
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.
57
6-
Top level code that contain a return statement (instead of, or in addition to
7-
await) will be detected as requiring being wrapped in async calls. This should
8-
be prevented as early return will not work.
8+
Some constructs (like top-level `return`, or `yield`) are taken care of
9+
explicitly to actually raise a SyntaxError and stay as close as possible to
10+
Python semantics.
911
"""
1012

1113

1214
import ast
1315
import sys
14-
import inspect
1516
from textwrap import dedent, indent
16-
from types import CodeType
1717

1818

19-
def _asyncio_runner(coro):
20-
"""
21-
Handler for asyncio autoawait
22-
"""
23-
import asyncio
19+
class _AsyncIORunner:
20+
21+
def __call__(self, coro):
22+
"""
23+
Handler for asyncio autoawait
24+
"""
25+
import asyncio
26+
27+
return asyncio.get_event_loop().run_until_complete(coro)
2428

25-
return asyncio.get_event_loop().run_until_complete(coro)
29+
def __str__(self):
30+
return 'asyncio'
31+
32+
_asyncio_runner = _AsyncIORunner()
2633

2734

2835
def _curio_runner(coroutine):
@@ -36,12 +43,14 @@ def _curio_runner(coroutine):
3643

3744
def _trio_runner(async_fn):
3845
import trio
46+
3947
async def loc(coro):
4048
"""
4149
We need the dummy no-op async def to protect from
4250
trio's internal. See https://github.com/python-trio/trio/issues/89
4351
"""
4452
return await coro
53+
4554
return trio.run(loc, async_fn)
4655

4756

@@ -60,7 +69,9 @@ def _pseudo_sync_runner(coro):
6069
return exc.value
6170
else:
6271
# 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+
)
6475

6576

6677
def _asyncify(code: str) -> str:
@@ -69,7 +80,7 @@ def _asyncify(code: str) -> str:
6980
And setup a bit of context to run it later.
7081
"""
7182
res = dedent(
72-
"""
83+
"""
7384
async def __wrapper__():
7485
try:
7586
{usercode}
@@ -86,12 +97,13 @@ class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):
8697
the implementation involves wrapping the repl in an async function, it
8798
is erroneously allowed (e.g. yield or return at the top level)
8899
"""
100+
89101
def generic_visit(self, node):
90102
func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
91103
invalid_types = (ast.Return, ast.Yield, ast.YieldFrom)
92104

93105
if isinstance(node, func_types):
94-
return # Don't recurse into functions
106+
return # Don't recurse into functions
95107
elif isinstance(node, invalid_types):
96108
raise SyntaxError()
97109
else:

IPython/core/interactiveshell.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2806,7 +2806,7 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr
28062806
self.events.trigger('post_run_cell', result)
28072807
return result
28082808

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):
28102810
"""Internal method to run a complete IPython cell."""
28112811
coro = self.run_cell_async(
28122812
raw_cell,
@@ -2815,11 +2815,16 @@ def _run_cell(self, raw_cell, store_history, silent, shell_futures):
28152815
shell_futures=shell_futures,
28162816
)
28172817

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.
28182822
try:
28192823
interactivity = coro.send(None)
28202824
except StopIteration as exc:
28212825
return exc.value
28222826

2827+
# if code was not async, sending `None` was actually executing the code.
28232828
if isinstance(interactivity, ExecutionResult):
28242829
return interactivity
28252830

@@ -3159,7 +3164,6 @@ def _async_exec(self, code_obj: types.CodeType, user_ns: dict):
31593164

31603165
return eval(code_obj, user_ns)
31613166

3162-
@asyncio.coroutine
31633167
def run_code(self, code_obj, result=None, *, async_=False):
31643168
"""Execute a code object.
31653169

IPython/core/magics/basic.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,12 @@ def autoawait(self, parameter_s):
632632
runner, and activate autoawait.
633633
634634
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+
"""
636641

637642
param = parameter_s.strip()
638643
d = {True: "on", False: "off"}

IPython/terminal/embed.py

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,6 @@
1919
from traitlets import Bool, CBool, Unicode
2020
from IPython.utils.io import ask_yes_no
2121

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-
3922
class KillEmbedded(Exception):pass
4023

4124
# kept for backward compatibility as IPython 6 was released with
@@ -400,12 +383,11 @@ def embed(**kwargs):
400383
cls = type(saved_shell_instance)
401384
cls.clear_instance()
402385
frame = sys._getframe(1)
403-
with new_context():
404-
shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
405-
frame.f_code.co_filename, frame.f_lineno), **kwargs)
406-
shell(header=header, stack_depth=2, compile_flags=compile_flags,
407-
_call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
408-
InteractiveShellEmbed.clear_instance()
386+
shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
387+
frame.f_code.co_filename, frame.f_lineno), **kwargs)
388+
shell(header=header, stack_depth=2, compile_flags=compile_flags,
389+
_call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
390+
InteractiveShellEmbed.clear_instance()
409391
#restore previous instance
410392
if saved_shell_instance is not None:
411393
cls = type(saved_shell_instance)

docs/source/interactive/autoawait.rst

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
Asynchronous in REPL: Autoawait
44
===============================
55

6+
.. note::
7+
8+
This feature is experimental and behavior can change betwen python and
9+
IPython version without prior deprecation.
10+
611
Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the
712
ability to run asynchronous code from the REPL. Constructs which are
813
:exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython.
@@ -12,10 +17,10 @@ notebook interface or any other frontend using the Jupyter protocol will need to
1217
use a newer version of IPykernel. The details of how async code runs in
1318
IPykernel will differ between IPython, IPykernel and their versions.
1419

15-
When a supported library is used, IPython will automatically `await` Futures and
16-
Coroutines in the REPL. This will happen if an :ref:`await <await>` (or any
17-
other async constructs like async-with, async-for) is use at top level scope, or
18-
if any structure valid only in `async def
20+
When a supported library is used, IPython will automatically allow Futures and
21+
Coroutines in the REPL to be ``await`` ed. This will happen if an :ref:`await
22+
<await>` (or any other async constructs like async-with, async-for) is use at
23+
top level scope, or if any structure valid only in `async def
1924
<https://docs.python.org/3/reference/compound_stmts.html#async-def>`_ function
2025
context are present. For example, the following being a syntax error in the
2126
Python REPL::
@@ -58,15 +63,13 @@ use the :magic:`%autoawait` magic to toggle the behavior at runtime::
5863
In [1]: %autoawait False
5964

6065
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`
6267

6368

6469

6570
By default IPython will assume integration with Python's provided
6671
:mod:`asyncio`, but integration with other libraries is provided. In particular
67-
we provide experimental integration with the ``curio`` and ``trio`` library, the
68-
later one being necessary if you require the ability to do nested call of
69-
IPython's ``embed()`` functionality.
72+
we provide experimental integration with the ``curio`` and ``trio`` library.
7073

7174
You can switch current integration by using the
7275
``c.InteractiveShell.loop_runner`` option or the ``autoawait <name
@@ -111,7 +114,7 @@ other features of IPython and various registered extensions. In particular if yo
111114
are a direct or indirect user of the AST transformers, these may not apply to
112115
your code.
113116

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
115118
in the background, so top level asynchronous code must finish for the REPL to
116119
allow you to enter more code. As with usual Python semantic, the awaitables are
117120
started only when awaited for the first time. That is to say, in first example,
@@ -122,25 +125,20 @@ Effects on IPython.embed()
122125
==========================
123126

124127
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
126-
loops can't be nested, ``IPython.embed()`` default to a pseudo-synchronous mode,
127-
where async code is not allowed. This mode is available in classical IPython
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-
128+
a loop to run. By default IPython will use a fake coroutine runner which should
129+
allow ``IPython.embed()`` to be nested. Though this will prevent usage of the
130+
``autoawait`` feature when using IPython embed.
135131

132+
You can set explicitly a coroutine runner for ``embed()`` if you desire to run
133+
asynchronous code, the exact behavior is though undefined.
136134

137135
Internals
138136
=========
139137

140138
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.
142-
It is interesting to understand how this works in order to comprehend potential
143-
bugs, or provide a custom runner.
139+
3.7) we have to rely to a number of complex workaround and heuristic to allow
140+
this to happen. It is interesting to understand how this works in order to
141+
comprehend potential bugs, or provide a custom runner.
144142

145143
Among the many approaches that are at our disposition, we find only one that
146144
suited out need. Under the hood we use the code object from a async-def function
@@ -168,8 +166,10 @@ On top of the above there are significant modification to the AST of
168166
significant overhead to this kind of code.
169167

170168
By default the generated coroutine function will be consumed by Asyncio's
171-
``loop_runner = asyncio.get_evenloop().run_until_complete()`` method. It is
172-
though possible to provide your own.
169+
``loop_runner = asyncio.get_evenloop().run_until_complete()`` method if
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.
173173

174174
A loop runner is a *synchronous* function responsible from running a coroutine
175175
object.
@@ -208,3 +208,6 @@ We can set it up by passing it to ``%autoawait``::
208208
Asynchronous programming in python (and in particular in the REPL) is still a
209209
relatively young subject. We expect some code to not behave as you expect, so
210210
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.

docs/source/whatsnew/pr/await-repl.rst

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ yourself. To know more read the :ref:`autoawait` section of our docs, see
2323
...
2424
}
2525

26+
.. note::
27+
28+
Async integration is experimental code, behavior may change or be removed
29+
between Python and IPython versions without warnings.
2630

2731
Integration is by default with `asyncio`, but other libraries can be configured,
2832
like ``curio`` or ``trio``, to improve concurrency in the REPL::
@@ -57,13 +61,33 @@ See :ref:`autoawait` for more information.
5761
Asynchronous code in a Notebook interface or any other frontend using the
5862
Jupyter Protocol will need further updates of the IPykernel package.
5963

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.
6072

6173
Change to Nested Embed
6274
----------------------
6375

64-
The introduction of the ability to run async code had ripple effect on the
65-
ability to use nested IPython. You may need to install the ``trio`` library
66-
(version 0.5 at the time of this writing) to
67-
have this feature working.
76+
The introduction of the ability to run async code had some effect on the
77+
``IPython.embed()`` API. By default embed will not allow you to run asynchronous
78+
code unless a event loop is specified.
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.
6885

86+
Thanks
87+
------
6988

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.

0 commit comments

Comments
 (0)