##// END OF EJS Templates
Prototype async REPL using IPython, take III...
Matthias Bussonnier -
Show More
@@ -0,0 +1,88 b''
1 """
2 Async helper function that are invalid syntax on Python 3.5 and below.
3
4 Known limitation and possible improvement.
5
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.
9 """
10
11
12
13 import ast
14 import sys
15 import inspect
16 from textwrap import dedent, indent
17 from types import CodeType
18
19
20 def _asyncio_runner(coro):
21 """
22 Handler for asyncio autoawait
23 """
24 import asyncio
25 return asyncio.get_event_loop().run_until_complete(coro)
26
27
28 def _curio_runner(coroutine):
29 """
30 handler for curio autoawait
31 """
32 import curio
33 return curio.run(coroutine)
34
35
36 if sys.version_info > (3, 5):
37 # nose refuses to avoid this file and async def is invalidsyntax
38 s = dedent('''
39 def _trio_runner(function):
40 import trio
41 async def loc(coro):
42 """
43 We need the dummy no-op async def to protect from
44 trio's internal. See https://github.com/python-trio/trio/issues/89
45 """
46 return await coro
47 return trio.run(loc, function)
48 ''')
49 exec(s, globals(), locals())
50
51
52 def _asyncify(code: str) -> str:
53 """wrap code in async def definition.
54
55 And setup a bit of context to run it later.
56 """
57 res = dedent("""
58 async def __wrapper__():
59 try:
60 {usercode}
61 finally:
62 locals()
63 """).format(usercode=indent(code, ' ' * 8)[8:])
64 return res
65
66
67 def _should_be_async(cell: str) -> bool:
68 """Detect if a block of code need to be wrapped in an `async def`
69
70 Attempt to parse the block of code, it it compile we're fine.
71 Otherwise we wrap if and try to compile.
72
73 If it works, assume it should be async. Otherwise Return False.
74
75 Not handled yet: If the block of code has a return statement as the top
76 level, it will be seen as async. This is a know limitation.
77 """
78
79 try:
80 ast.parse(cell)
81 return False
82 except SyntaxError:
83 try:
84 ast.parse(_asyncify(cell))
85 except SyntaxError:
86 return False
87 return True
88 return False
@@ -0,0 +1,52 b''
1 """
2 Test for async helpers.
3
4 Should only trigger on python 3.5+ or will have syntax errors.
5 """
6
7 import sys
8 import nose.tools as nt
9 from textwrap import dedent
10 from unittest import TestCase
11
12 ip = get_ipython()
13 iprc = lambda x: ip.run_cell(dedent(x))
14
15 if sys.version_info > (3,5):
16 from IPython.core.async_helpers import _should_be_async
17
18 class AsyncTest(TestCase):
19
20 def test_should_be_async(self):
21 nt.assert_false(_should_be_async("False"))
22 nt.assert_true(_should_be_async("await bar()"))
23 nt.assert_true(_should_be_async("x = await bar()"))
24 nt.assert_false(_should_be_async(dedent("""
25 async def awaitable():
26 pass
27 """)))
28
29 def test_execute(self):
30 iprc("""
31 import asyncio
32 await asyncio.sleep(0.001)
33 """)
34
35 def test_autoawait(self):
36 ip.run_cell('%autoawait False')
37 ip.run_cell('%autoawait True')
38 iprc('''
39 from asyncio import sleep
40 await.sleep(0.1)
41 ''')
42
43 def test_autoawait_curio(self):
44 ip.run_cell('%autoawait curio')
45
46 def test_autoawait_trio(self):
47 ip.run_cell('%autoawait trio')
48
49 def tearDown(self):
50 ip.loop_runner = 'asyncio'
51
52
@@ -0,0 +1,186 b''
1
2 .. autoawait:
3
4 Asynchronous in REPL: Autoawait
5 ===============================
6
7 Starting with IPython 6.0, and when user Python 3.6 and above, IPython offer the
8 ability to run asynchronous code from the REPL. constructs which are
9 :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython.
10
11 When a supported libray is used, IPython will automatically `await` Futures
12 and Coroutines in the REPL. This will happen if an :ref:`await <await>` (or `async`) is
13 use at top level scope, or if any structure valid only in `async def
14 <https://docs.python.org/3/reference/compound_stmts.html#async-def>`_ function
15 context are present. For example, the following being a syntax error in the
16 Python REPL::
17
18 Python 3.6.0
19 [GCC 4.2.1]
20 Type "help", "copyright", "credits" or "license" for more information.
21 >>> import aiohttp
22 >>> result = aiohttp.get('https://api.github.com')
23 >>> response = await result
24 File "<stdin>", line 1
25 response = await result
26 ^
27 SyntaxError: invalid syntax
28
29 Should behave as expected in the IPython REPL::
30
31 Python 3.6.0
32 Type 'copyright', 'credits' or 'license' for more information
33 IPython 6.0.0.dev -- An enhanced Interactive Python. Type '?' for help.
34
35 In [1]: import aiohttp
36 ...: result = aiohttp.get('https://api.github.com')
37
38 In [2]: response = await result
39 <pause for a few 100s ms>
40
41 In [3]: await response.json()
42 Out[3]:
43 {'authorizations_url': 'https://api.github.com/authorizations',
44 'code_search_url': 'https://api.github.com/search/code?q={query}...',
45 ...
46 }
47
48
49 You can use the ``c.InteractiveShell.autoawait`` configuration option and set it
50 to :any:`False` to deactivate automatic wrapping of asynchronous code. You can also
51 use the :magic:`%autoawait` magic to toggle the behavior at runtime::
52
53 In [1]: %autoawait False
54
55 In [2]: %autoawait
56 IPython autoawait is `Off`, and set to use `IPython.core.interactiveshell._asyncio_runner`
57
58
59
60 By default IPython will assume integration with Python's provided
61 :mod:`asyncio`, but integration with other libraries is provided. In particular
62 we provide experimental integration with the ``curio`` and ``trio`` library.
63
64 You can switch current integration by using the
65 ``c.InteractiveShell.loop_runner`` option or the ``autoawait <name
66 integration>`` magic.
67
68 For example::
69
70 In [1]: %autoawait trio
71
72 In [2]: import trio
73
74 In [3]: async def child(i):
75 ...: print(" child %s goes to sleep"%i)
76 ...: await trio.sleep(2)
77 ...: print(" child %s wakes up"%i)
78
79 In [4]: print('parent start')
80 ...: async with trio.open_nursery() as n:
81 ...: for i in range(5):
82 ...: n.spawn(child, i)
83 ...: print('parent end')
84 parent start
85 child 2 goes to sleep
86 child 0 goes to sleep
87 child 3 goes to sleep
88 child 1 goes to sleep
89 child 4 goes to sleep
90 <about 2 seconds pause>
91 child 2 wakes up
92 child 1 wakes up
93 child 0 wakes up
94 child 3 wakes up
95 child 4 wakes up
96 parent end
97
98
99 In the above example, ``async with`` at top level scope is a syntax error in
100 Python.
101
102 Using this mode can have unexpected consequences if used in interaction with
103 other features of IPython and various registered extensions. In particular if you
104 are a direct or indirect user of the AST transformers, these may not apply to
105 your code.
106
107 The default loop, or runner does not run in the background, so top level
108 asynchronous code must finish for the REPL to allow you to enter more code. As
109 with usual Python semantic, the awaitables are started only when awaited for the
110 first time. That is to say, in first example, no network request is done between
111 ``In[1]`` and ``In[2]``.
112
113
114 Internals
115 =========
116
117 As running asynchronous code is not supported in interactive REPL as of Python
118 3.6 we have to rely to a number of complex workaround to allow this to happen.
119 It is interesting to understand how this works in order to understand potential
120 bugs, or provide a custom runner.
121
122 Among the many approaches that are at our disposition, we find only one that
123 suited out need. Under the hood we :ct the code object from a async-def function
124 and run it in global namesapace after modifying the ``__code__`` object.::
125
126 async def inner_async():
127 locals().update(**global_namespace)
128 #
129 # here is user code
130 #
131 return last_user_statement
132 codeobj = modify(inner_async.__code__)
133 coroutine = eval(codeobj, user_ns)
134 display(loop_runner(coroutine))
135
136
137
138 The first thing you'll notice is that unlike classical ``exec``, there is only
139 one name space. Second, user code runs in a function scope, and not a module
140 scope.
141
142 On top of the above there are significant modification to the AST of
143 ``function``, and ``loop_runner`` can be arbitrary complex. So there is a
144 significant overhead to this kind of code.
145
146 By default the generated coroutine function will be consumed by Asyncio's
147 ``loop_runner = asyncio.get_evenloop().run_until_complete()`` method. It is
148 though possible to provide your own.
149
150 A loop runner is a *synchronous* function responsible from running a coroutine
151 object.
152
153 The runner is responsible from ensuring that ``coroutine`` run to completion,
154 and should return the result of executing the coroutine. Let's write a
155 runner for ``trio`` that print a message when used as an exercise, ``trio`` is
156 special as it usually prefer to run a function object and make a coroutine by
157 itself, we can get around this limitation by wrapping it in an async-def without
158 parameters and passing this value to ``trio``::
159
160
161 In [1]: import trio
162 ...: from types import CoroutineType
163 ...:
164 ...: def trio_runner(coro:CoroutineType):
165 ...: print('running asynchronous code')
166 ...: async def corowrap(coro):
167 ...: return await coro
168 ...: return trio.run(corowrap, coro)
169
170 We can set it up by passing it to ``%autoawait``::
171
172 In [2]: %autoawait trio_runner
173
174 In [3]: async def async_hello(name):
175 ...: await trio.sleep(1)
176 ...: print(f'Hello {name} world !')
177 ...: await trio.sleep(1)
178
179 In [4]: await async_hello('async')
180 running asynchronous code
181 Hello async world !
182
183
184 Asynchronous programming in python (and in particular in the REPL) is still a
185 relatively young subject. Feel free to contribute improvements to this codebase
186 and give us feedback.
@@ -0,0 +1,55 b''
1 Await REPL
2 ----------
3
4 :ghpull:`10390` introduced the ability to ``await`` Futures and
5 Coroutines in the REPL. For example::
6
7 Python 3.6.0
8 Type 'copyright', 'credits' or 'license' for more information
9 IPython 6.0.0.dev -- An enhanced Interactive Python. Type '?' for help.
10
11 In [1]: import aiohttp
12 ...: result = aiohttp.get('https://api.github.com')
13
14 In [2]: response = await result
15 <pause for a few 100s ms>
16
17 In [3]: await response.json()
18 Out[3]:
19 {'authorizations_url': 'https://api.github.com/authorizations',
20 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}',
21 ...
22 }
23
24
25 Integration is by default with `asyncio`, but other libraries can be configured,
26 like ``curio`` or ``trio``, to improve concurrency in the REPL::
27
28 In [1]: %autoawait trio
29
30 In [2]: import trio
31
32 In [3]: async def child(i):
33 ...: print(" child %s goes to sleep"%i)
34 ...: await trio.sleep(2)
35 ...: print(" child %s wakes up"%i)
36
37 In [4]: print('parent start')
38 ...: async with trio.open_nursery() as n:
39 ...: for i in range(3):
40 ...: n.spawn(child, i)
41 ...: print('parent end')
42 parent start
43 child 2 goes to sleep
44 child 0 goes to sleep
45 child 1 goes to sleep
46 <about 2 seconds pause>
47 child 2 wakes up
48 child 1 wakes up
49 child 0 wakes up
50 parent end
51
52 See :ref:`autoawait` for more information.
53
54
55
@@ -13,6 +13,7 b''
13
13
14 import abc
14 import abc
15 import ast
15 import ast
16 import asyncio
16 import atexit
17 import atexit
17 import builtins as builtin_mod
18 import builtins as builtin_mod
18 import functools
19 import functools
@@ -30,6 +31,7 b' from io import open as io_open'
30 from pickleshare import PickleShareDB
31 from pickleshare import PickleShareDB
31
32
32 from traitlets.config.configurable import SingletonConfigurable
33 from traitlets.config.configurable import SingletonConfigurable
34 from traitlets.utils.importstring import import_item
33 from IPython.core import oinspect
35 from IPython.core import oinspect
34 from IPython.core import magic
36 from IPython.core import magic
35 from IPython.core import page
37 from IPython.core import page
@@ -73,7 +75,7 b' from IPython.utils.text import format_screen, LSString, SList, DollarFormatter'
73 from IPython.utils.tempdir import TemporaryDirectory
75 from IPython.utils.tempdir import TemporaryDirectory
74 from traitlets import (
76 from traitlets import (
75 Integer, Bool, CaselessStrEnum, Enum, List, Dict, Unicode, Instance, Type,
77 Integer, Bool, CaselessStrEnum, Enum, List, Dict, Unicode, Instance, Type,
76 observe, default,
78 observe, default, validate, Any
77 )
79 )
78 from warnings import warn
80 from warnings import warn
79 from logging import error
81 from logging import error
@@ -113,6 +115,102 b' else:'
113 _single_targets_nodes = (ast.AugAssign, )
115 _single_targets_nodes = (ast.AugAssign, )
114
116
115 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118 # Await Helpers
119 #-----------------------------------------------------------------------------
120
121 def removed_co_newlocals(function:types.FunctionType) -> types.FunctionType:
122 """Return a function that do not create a new local scope.
123
124 Given a function, create a clone of this function where the co_newlocal flag
125 has been removed, making this function code actually run in the sourounding
126 scope.
127
128 We need this in order to run asynchronous code in user level namespace.
129 """
130 from types import CodeType, FunctionType
131 CO_NEWLOCALS = 0x0002
132 code = function.__code__
133 new_code = CodeType(
134 code.co_argcount,
135 code.co_kwonlyargcount,
136 code.co_nlocals,
137 code.co_stacksize,
138 code.co_flags & ~CO_NEWLOCALS,
139 code.co_code,
140 code.co_consts,
141 code.co_names,
142 code.co_varnames,
143 code.co_filename,
144 code.co_name,
145 code.co_firstlineno,
146 code.co_lnotab,
147 code.co_freevars,
148 code.co_cellvars
149 )
150 return FunctionType(new_code, globals(), function.__name__, function.__defaults__)
151
152
153 if sys.version_info > (3,5):
154 from .async_helpers import (_asyncio_runner, _curio_runner, _trio_runner,
155 _should_be_async, _asyncify
156 )
157 else :
158 _asyncio_runner = _curio_runner = _trio_runner = None
159
160 def _should_be_async(whatever:str)->bool:
161 return False
162
163
164 def _ast_asyncify(cell:str, wrapper_name:str) -> ast.Module:
165 """
166 Parse a cell with top-level await and modify the AST to be able to run it later.
167
168 Parameter
169 ---------
170
171 cell: str
172 The code cell to asyncronify
173 wrapper_name: str
174 The name of the function to be used to wrap the passed `cell`. It is
175 advised to **not** use a python identifier in order to not pollute the
176 global namespace in which the function will be ran.
177
178 Return
179 ------
180
181 A module object AST containing **one** function named `wrapper_name`.
182
183 The given code is wrapped in a async-def function, parsed into an AST, and
184 the resulting function definition AST is modified to return the last
185 expression.
186
187 The last expression or await node is moved into a return statement at the
188 end of the function, and removed from its original location. If the last
189 node is not Expr or Await nothing is done.
190
191 The function `__code__` will need to be later modified (by
192 ``removed_co_newlocals``) in a subsequent step to not create new `locals()`
193 meaning that the local and global scope are the same, ie as if the body of
194 the function was at module level.
195
196 Lastly a call to `locals()` is made just before the last expression of the
197 function, or just after the last assignment or statement to make sure the
198 global dict is updated as python function work with a local fast cache which
199 is updated only on `local()` calls.
200 """
201
202 from ast import Expr, Await, Return
203 tree = ast.parse(_asyncify(cell))
204
205 function_def = tree.body[0]
206 function_def.name = wrapper_name
207 try_block = function_def.body[0]
208 lastexpr = try_block.body[-1]
209 if isinstance(lastexpr, (Expr, Await)):
210 try_block.body[-1] = Return(lastexpr.value)
211 ast.fix_missing_locations(tree)
212 return tree
213 #-----------------------------------------------------------------------------
116 # Globals
214 # Globals
117 #-----------------------------------------------------------------------------
215 #-----------------------------------------------------------------------------
118
216
@@ -258,6 +356,40 b' class InteractiveShell(SingletonConfigurable):'
258 """
356 """
259 ).tag(config=True)
357 ).tag(config=True)
260
358
359 autoawait = Bool(True, help=
360 """
361 Automatically run await statement in the top level repl.
362 """
363 ).tag(config=True)
364
365 loop_runner_map ={
366 'asyncio':_asyncio_runner,
367 'curio':_curio_runner,
368 'trio':_trio_runner,
369 }
370
371 loop_runner = Any(default_value="IPython.core.interactiveshell._asyncio_runner",
372 allow_none=True,
373 help="""Select the loop runner that will be used to execute top-level asynchronous code"""
374 ).tag(config=True)
375
376 @default('loop_runner')
377 def _default_loop_runner(self):
378 return import_item("IPython.core.interactiveshell._asyncio_runner")
379
380 @validate('loop_runner')
381 def _import_runner(self, proposal):
382 if isinstance(proposal.value, str):
383 if proposal.value in self.loop_runner_map:
384 return self.loop_runner_map[proposal.value]
385 runner = import_item(proposal.value)
386 if not callable(runner):
387 raise ValueError('loop_runner must be callable')
388 return runner
389 if not callable(proposal.value):
390 raise ValueError('loop_runner must be callable')
391 return proposal.value
392
261 automagic = Bool(True, help=
393 automagic = Bool(True, help=
262 """
394 """
263 Enable magic commands to be called without the leading %.
395 Enable magic commands to be called without the leading %.
@@ -1449,6 +1581,7 b' class InteractiveShell(SingletonConfigurable):'
1449 parent = None
1581 parent = None
1450 obj = None
1582 obj = None
1451
1583
1584
1452 # Look for the given name by splitting it in parts. If the head is
1585 # Look for the given name by splitting it in parts. If the head is
1453 # found, then we look for all the remaining parts as members, and only
1586 # found, then we look for all the remaining parts as members, and only
1454 # declare success if we can find them all.
1587 # declare success if we can find them all.
@@ -1984,7 +2117,6 b' class InteractiveShell(SingletonConfigurable):'
1984 self.set_hook('complete_command', cd_completer, str_key = '%cd')
2117 self.set_hook('complete_command', cd_completer, str_key = '%cd')
1985 self.set_hook('complete_command', reset_completer, str_key = '%reset')
2118 self.set_hook('complete_command', reset_completer, str_key = '%reset')
1986
2119
1987
1988 @skip_doctest
2120 @skip_doctest
1989 def complete(self, text, line=None, cursor_pos=None):
2121 def complete(self, text, line=None, cursor_pos=None):
1990 """Return the completed text and a list of completions.
2122 """Return the completed text and a list of completions.
@@ -2667,14 +2799,36 b' class InteractiveShell(SingletonConfigurable):'
2667 return result
2799 return result
2668
2800
2669 def _run_cell(self, raw_cell, store_history, silent, shell_futures):
2801 def _run_cell(self, raw_cell, store_history, silent, shell_futures):
2670 """Internal method to run a complete IPython cell.
2802 """Internal method to run a complete IPython cell."""
2803 return self.loop_runner(
2804 self.run_cell_async(
2805 raw_cell,
2806 store_history=store_history,
2807 silent=silent,
2808 shell_futures=shell_futures,
2809 )
2810 )
2811
2812 @asyncio.coroutine
2813 def run_cell_async(self, raw_cell, store_history=False, silent=False, shell_futures=True):
2814 """Run a complete IPython cell asynchronously.
2671
2815
2672 Parameters
2816 Parameters
2673 ----------
2817 ----------
2674 raw_cell : str
2818 raw_cell : str
2819 The code (including IPython code such as %magic functions) to run.
2675 store_history : bool
2820 store_history : bool
2821 If True, the raw and translated cell will be stored in IPython's
2822 history. For user code calling back into IPython's machinery, this
2823 should be set to False.
2676 silent : bool
2824 silent : bool
2825 If True, avoid side-effects, such as implicit displayhooks and
2826 and logging. silent=True forces store_history=False.
2677 shell_futures : bool
2827 shell_futures : bool
2828 If True, the code will share future statements with the interactive
2829 shell. It will both be affected by previous __future__ imports, and
2830 any __future__ imports in the code will affect the shell. If False,
2831 __future__ imports are not shared in either direction.
2678
2832
2679 Returns
2833 Returns
2680 -------
2834 -------
@@ -2749,13 +2903,33 b' class InteractiveShell(SingletonConfigurable):'
2749 # compiler
2903 # compiler
2750 compiler = self.compile if shell_futures else CachingCompiler()
2904 compiler = self.compile if shell_futures else CachingCompiler()
2751
2905
2906 _run_async = False
2907
2752 with self.builtin_trap:
2908 with self.builtin_trap:
2753 cell_name = self.compile.cache(cell, self.execution_count)
2909 cell_name = self.compile.cache(cell, self.execution_count)
2754
2910
2755 with self.display_trap:
2911 with self.display_trap:
2756 # Compile to bytecode
2912 # Compile to bytecode
2757 try:
2913 try:
2758 code_ast = compiler.ast_parse(cell, filename=cell_name)
2914 if self.autoawait and _should_be_async(cell):
2915 # the code AST below will not be user code: we wrap it
2916 # in an `async def`. This will likely make some AST
2917 # transformer below miss some transform opportunity and
2918 # introduce a small coupling to run_code (in which we
2919 # bake some assumptions of what _ast_asyncify returns.
2920 # they are ways around (like grafting part of the ast
2921 # later:
2922 # - Here, return code_ast.body[0].body[1:-1], as well
2923 # as last expression in return statement which is
2924 # the user code part.
2925 # - Let it go through the AST transformers, and graft
2926 # - it back after the AST transform
2927 # But that seem unreasonable, at least while we
2928 # do not need it.
2929 code_ast = _ast_asyncify(cell, 'async-def-wrapper')
2930 _run_async = True
2931 else:
2932 code_ast = compiler.ast_parse(cell, filename=cell_name)
2759 except self.custom_exceptions as e:
2933 except self.custom_exceptions as e:
2760 etype, value, tb = sys.exc_info()
2934 etype, value, tb = sys.exc_info()
2761 self.CustomTB(etype, value, tb)
2935 self.CustomTB(etype, value, tb)
@@ -2780,9 +2954,11 b' class InteractiveShell(SingletonConfigurable):'
2780 self.displayhook.exec_result = result
2954 self.displayhook.exec_result = result
2781
2955
2782 # Execute the user code
2956 # Execute the user code
2783 interactivity = 'none' if silent else self.ast_node_interactivity
2957 interactivity = "none" if silent else self.ast_node_interactivity
2784 has_raised = self.run_ast_nodes(code_ast.body, cell_name,
2958 if _run_async:
2785 interactivity=interactivity, compiler=compiler, result=result)
2959 interactivity = 'async'
2960 has_raised = yield from self.run_ast_nodes(code_ast.body, cell_name,
2961 interactivity=interactivity, compiler=compiler, result=result)
2786
2962
2787 self.last_execution_succeeded = not has_raised
2963 self.last_execution_succeeded = not has_raised
2788 self.last_execution_result = result
2964 self.last_execution_result = result
@@ -2826,12 +3002,12 b' class InteractiveShell(SingletonConfigurable):'
2826 except Exception:
3002 except Exception:
2827 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
3003 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
2828 self.ast_transformers.remove(transformer)
3004 self.ast_transformers.remove(transformer)
2829
3005
2830 if self.ast_transformers:
3006 if self.ast_transformers:
2831 ast.fix_missing_locations(node)
3007 ast.fix_missing_locations(node)
2832 return node
3008 return node
2833
2834
3009
3010 @asyncio.coroutine
2835 def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='last_expr',
3011 def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='last_expr',
2836 compiler=compile, result=None):
3012 compiler=compile, result=None):
2837 """Run a sequence of AST nodes. The execution mode depends on the
3013 """Run a sequence of AST nodes. The execution mode depends on the
@@ -2852,6 +3028,12 b' class InteractiveShell(SingletonConfigurable):'
2852 are not displayed) 'last_expr_or_assign' will run the last expression
3028 are not displayed) 'last_expr_or_assign' will run the last expression
2853 or the last assignment. Other values for this parameter will raise a
3029 or the last assignment. Other values for this parameter will raise a
2854 ValueError.
3030 ValueError.
3031
3032 Experimental value: 'async' Will try to run top level interactive
3033 async/await code in default runner, this will not respect the
3034 interactivty setting and will only run the last node if it is an
3035 expression.
3036
2855 compiler : callable
3037 compiler : callable
2856 A function with the same interface as the built-in compile(), to turn
3038 A function with the same interface as the built-in compile(), to turn
2857 the AST nodes into code objects. Default is the built-in compile().
3039 the AST nodes into code objects. Default is the built-in compile().
@@ -2880,6 +3062,7 b' class InteractiveShell(SingletonConfigurable):'
2880 nodelist.append(nnode)
3062 nodelist.append(nnode)
2881 interactivity = 'last_expr'
3063 interactivity = 'last_expr'
2882
3064
3065 _async = False
2883 if interactivity == 'last_expr':
3066 if interactivity == 'last_expr':
2884 if isinstance(nodelist[-1], ast.Expr):
3067 if isinstance(nodelist[-1], ast.Expr):
2885 interactivity = "last"
3068 interactivity = "last"
@@ -2892,20 +3075,32 b' class InteractiveShell(SingletonConfigurable):'
2892 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
3075 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
2893 elif interactivity == 'all':
3076 elif interactivity == 'all':
2894 to_run_exec, to_run_interactive = [], nodelist
3077 to_run_exec, to_run_interactive = [], nodelist
3078 elif interactivity == 'async':
3079 _async = True
2895 else:
3080 else:
2896 raise ValueError("Interactivity was %r" % interactivity)
3081 raise ValueError("Interactivity was %r" % interactivity)
2897 try:
3082 try:
2898 for i, node in enumerate(to_run_exec):
3083 if _async:
2899 mod = ast.Module([node])
3084 # If interactivity is async the semantics of run_code are
2900 code = compiler(mod, cell_name, "exec")
3085 # completely different Skip usual machinery.
2901 if self.run_code(code, result):
3086 mod = ast.Module(nodelist)
2902 return True
3087 async_wrapper_code = compiler(mod, 'cell_name', 'exec')
2903
3088 exec(async_wrapper_code, self.user_global_ns, self.user_ns)
2904 for i, node in enumerate(to_run_interactive):
3089 async_code = removed_co_newlocals(self.user_ns.pop('async-def-wrapper')).__code__
2905 mod = ast.Interactive([node])
3090 if (yield from self.run_code(async_code, result, async_=True)):
2906 code = compiler(mod, cell_name, "single")
2907 if self.run_code(code, result):
2908 return True
3091 return True
3092 else:
3093 for i, node in enumerate(to_run_exec):
3094 mod = ast.Module([node])
3095 code = compiler(mod, cell_name, "exec")
3096 if (yield from self.run_code(code, result)):
3097 return True
3098
3099 for i, node in enumerate(to_run_interactive):
3100 mod = ast.Interactive([node])
3101 code = compiler(mod, cell_name, "single")
3102 if (yield from self.run_code(code, result)):
3103 return True
2909
3104
2910 # Flush softspace
3105 # Flush softspace
2911 if softspace(sys.stdout, 0):
3106 if softspace(sys.stdout, 0):
@@ -2928,7 +3123,23 b' class InteractiveShell(SingletonConfigurable):'
2928
3123
2929 return False
3124 return False
2930
3125
2931 def run_code(self, code_obj, result=None):
3126 def _async_exec(self, code_obj: types.CodeType, user_ns: dict):
3127 """
3128 Evaluate an asynchronous code object using a code runner
3129
3130 Fake asynchronous execution of code_object in a namespace via a proxy namespace.
3131
3132 Returns coroutine object, which can be executed via async loop runner
3133
3134 WARNING: The semantics of `async_exec` are quite different from `exec`,
3135 in particular you can only pass a single namespace. It also return a
3136 handle to the value of the last things returned by code_object.
3137 """
3138
3139 return eval(code_obj, user_ns)
3140
3141 @asyncio.coroutine
3142 def run_code(self, code_obj, result=None, *, async_=False):
2932 """Execute a code object.
3143 """Execute a code object.
2933
3144
2934 When an exception occurs, self.showtraceback() is called to display a
3145 When an exception occurs, self.showtraceback() is called to display a
@@ -2940,6 +3151,8 b' class InteractiveShell(SingletonConfigurable):'
2940 A compiled code object, to be executed
3151 A compiled code object, to be executed
2941 result : ExecutionResult, optional
3152 result : ExecutionResult, optional
2942 An object to store exceptions that occur during execution.
3153 An object to store exceptions that occur during execution.
3154 async_ : Bool (Experimental)
3155 Attempt to run top-level asynchronous code in a default loop.
2943
3156
2944 Returns
3157 Returns
2945 -------
3158 -------
@@ -2957,8 +3170,12 b' class InteractiveShell(SingletonConfigurable):'
2957 try:
3170 try:
2958 try:
3171 try:
2959 self.hooks.pre_run_code_hook()
3172 self.hooks.pre_run_code_hook()
2960 #rprint('Running code', repr(code_obj)) # dbg
3173 if async_:
2961 exec(code_obj, self.user_global_ns, self.user_ns)
3174 last_expr = (yield from self._async_exec(code_obj, self.user_ns))
3175 code = compile('last_expr', 'fake', "single")
3176 exec(code, {'last_expr': last_expr})
3177 else:
3178 exec(code_obj, self.user_global_ns, self.user_ns)
2962 finally:
3179 finally:
2963 # Reset our crash handler in place
3180 # Reset our crash handler in place
2964 sys.excepthook = old_excepthook
3181 sys.excepthook = old_excepthook
@@ -2,19 +2,20 b''
2
2
3
3
4 import argparse
4 import argparse
5 import textwrap
5 from logging import error
6 import io
6 import io
7 import sys
8 from pprint import pformat
7 from pprint import pformat
8 import textwrap
9 import sys
10 from warnings import warn
9
11
12 from traitlets.utils.importstring import import_item
10 from IPython.core import magic_arguments, page
13 from IPython.core import magic_arguments, page
11 from IPython.core.error import UsageError
14 from IPython.core.error import UsageError
12 from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
15 from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
13 from IPython.utils.text import format_screen, dedent, indent
16 from IPython.utils.text import format_screen, dedent, indent
14 from IPython.testing.skipdoctest import skip_doctest
17 from IPython.testing.skipdoctest import skip_doctest
15 from IPython.utils.ipstruct import Struct
18 from IPython.utils.ipstruct import Struct
16 from warnings import warn
17 from logging import error
18
19
19
20
20 class MagicsDisplay(object):
21 class MagicsDisplay(object):
@@ -379,6 +380,64 b' Currently the magic system has the following functions:""",'
379 xmode_switch_err('user')
380 xmode_switch_err('user')
380
381
381 @line_magic
382 @line_magic
383 def autoawait(self, parameter_s):
384 """
385 Allow to change the status of the autoawait option.
386
387 This allow you to set a specific asynchronous code runner.
388
389 If no value is passed, print the currently used asynchronous integration
390 and whether it is activated.
391
392 It can take a number of value evaluated in the following order:
393
394 - False/false/off deactivate autoawait integration
395 - True/true/on activate autoawait integration using configured default
396 loop
397 - asyncio/curio/trio activate autoawait integration and use integration
398 with said library.
399
400 If the passed parameter does not match any of the above and is a python
401 identifier, get said object from user namespace and set it as the
402 runner, and activate autoawait.
403
404 If the object is a fully qualified object name, attempt to import it and
405 set it as the runner, and activate autoawait."""
406
407 param = parameter_s.strip()
408 d = {True: "on", False: "off"}
409
410 if not param:
411 print("IPython autoawait is `{}`, and set to use `{}`".format(
412 d[self.shell.autoawait],
413 self.shell.loop_runner
414 ))
415 return None
416
417 if param.lower() in ('false', 'off'):
418 self.shell.autoawait = False
419 return None
420 if param.lower() in ('true', 'on'):
421 self.shell.autoawait = True
422 return None
423
424 if param in self.shell.loop_runner_map:
425 self.shell.loop_runner = param
426 self.shell.autoawait = True
427 return None
428
429 if param in self.shell.user_ns :
430 self.shell.loop_runner = self.shell.user_ns[param]
431 self.shell.autoawait = True
432 return None
433
434 runner = import_item(param)
435
436 self.shell.loop_runner = runner
437 self.shell.autoawait = True
438
439
440 @line_magic
382 def pip(self, args=''):
441 def pip(self, args=''):
383 """
442 """
384 Intercept usage of ``pip`` in IPython and direct user to run command outside of IPython.
443 Intercept usage of ``pip`` in IPython and direct user to run command outside of IPython.
@@ -19,6 +19,23 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
22 class KillEmbedded(Exception):pass
39 class KillEmbedded(Exception):pass
23
40
24 # kept for backward compatibility as IPython 6 was released with
41 # kept for backward compatibility as IPython 6 was released with
@@ -366,6 +383,9 b' def embed(**kwargs):'
366 config = load_default_config()
383 config = load_default_config()
367 config.InteractiveShellEmbed = config.TerminalInteractiveShell
384 config.InteractiveShellEmbed = config.TerminalInteractiveShell
368 kwargs['config'] = config
385 kwargs['config'] = config
386 using = kwargs.get('using', 'trio')
387 if using :
388 kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor'}})
369 #save ps1/ps2 if defined
389 #save ps1/ps2 if defined
370 ps1 = None
390 ps1 = None
371 ps2 = None
391 ps2 = None
@@ -380,11 +400,12 b' def embed(**kwargs):'
380 cls = type(saved_shell_instance)
400 cls = type(saved_shell_instance)
381 cls.clear_instance()
401 cls.clear_instance()
382 frame = sys._getframe(1)
402 frame = sys._getframe(1)
383 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
403 with new_context():
384 frame.f_code.co_filename, frame.f_lineno), **kwargs)
404 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
385 shell(header=header, stack_depth=2, compile_flags=compile_flags,
405 frame.f_code.co_filename, frame.f_lineno), **kwargs)
386 _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
406 shell(header=header, stack_depth=2, compile_flags=compile_flags,
387 InteractiveShellEmbed.clear_instance()
407 _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
408 InteractiveShellEmbed.clear_instance()
388 #restore previous instance
409 #restore previous instance
389 if saved_shell_instance is not None:
410 if saved_shell_instance is not None:
390 cls = type(saved_shell_instance)
411 cls = type(saved_shell_instance)
@@ -72,6 +72,7 b' def test_nest_embed():'
72
72
73 child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor'],
73 child = pexpect.spawn(sys.executable, ['-m', 'IPython', '--colors=nocolor'],
74 env=env)
74 env=env)
75 child.timeout = 5
75 child.expect(ipy_prompt)
76 child.expect(ipy_prompt)
76 child.sendline("import IPython")
77 child.sendline("import IPython")
77 child.expect(ipy_prompt)
78 child.expect(ipy_prompt)
@@ -86,7 +87,8 b' def test_nest_embed():'
86 except pexpect.TIMEOUT as e:
87 except pexpect.TIMEOUT as e:
87 print(e)
88 print(e)
88 #child.interact()
89 #child.interact()
89 child.sendline("embed1 = get_ipython()"); child.expect(ipy_prompt)
90 child.sendline("embed1 = get_ipython()")
91 child.expect(ipy_prompt)
90 child.sendline("print('true' if embed1 is not ip0 else 'false')")
92 child.sendline("print('true' if embed1 is not ip0 else 'false')")
91 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
93 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
92 child.expect(ipy_prompt)
94 child.expect(ipy_prompt)
@@ -103,7 +105,8 b' def test_nest_embed():'
103 except pexpect.TIMEOUT as e:
105 except pexpect.TIMEOUT as e:
104 print(e)
106 print(e)
105 #child.interact()
107 #child.interact()
106 child.sendline("embed2 = get_ipython()"); child.expect(ipy_prompt)
108 child.sendline("embed2 = get_ipython()")
109 child.expect(ipy_prompt)
107 child.sendline("print('true' if embed2 is not embed1 else 'false')")
110 child.sendline("print('true' if embed2 is not embed1 else 'false')")
108 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
111 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
109 child.expect(ipy_prompt)
112 child.expect(ipy_prompt)
@@ -143,7 +143,8 b" today_fmt = '%B %d, %Y'"
143
143
144 # Exclude these glob-style patterns when looking for source files. They are
144 # Exclude these glob-style patterns when looking for source files. They are
145 # relative to the source/ directory.
145 # relative to the source/ directory.
146 exclude_patterns = ['whatsnew/pr']
146 exclude_patterns = ['whatsnew/pr/antigravity-feature.*',
147 'whatsnew/pr/incompat-switching-to-perl.*']
147
148
148
149
149 # If true, '()' will be appended to :func: etc. cross-reference text.
150 # If true, '()' will be appended to :func: etc. cross-reference text.
@@ -21,6 +21,7 b' done some work in the classic Python REPL.'
21 plotting
21 plotting
22 reference
22 reference
23 shell
23 shell
24 autoawait
24 tips
25 tips
25 python-ipython-diff
26 python-ipython-diff
26 magics
27 magics
@@ -11,6 +11,135 b' This document describes in-flight development work.'
11 `docs/source/whatsnew/pr` folder
11 `docs/source/whatsnew/pr` folder
12
12
13
13
14 Released .... ...., 2017
15
16
17 Need to be updated:
18
19 .. toctree::
20 :maxdepth: 2
21 :glob:
22
23 pr/*
24
25 IPython 6 feature a major improvement in the completion machinery which is now
26 capable of completing non-executed code. It is also the first version of IPython
27 to stop compatibility with Python 2, which is still supported on the bugfix only
28 5.x branch. Read below to have a non-exhaustive list of new features.
29
30 Make sure you have pip > 9.0 before upgrading.
31 You should be able to update by using:
32
33 .. code::
34
35 pip install ipython --upgrade
36
37 New completion API and Interface
38 --------------------------------
39
40 The completer Completion API has seen an overhaul, and the new completer have
41 plenty of improvement both from the end users of terminal IPython or for
42 consumers of the API.
43
44 This new API is capable of pulling completions from :any:`jedi`, thus allowing
45 type inference on non-executed code. If :any:`jedi` is installed completion like
46 the following are now becoming possible without code evaluation:
47
48 >>> data = ['Number of users', 123_456]
49 ... data[0].<tab>
50
51 That is to say, IPython is now capable of inferring that `data[0]` is a string,
52 and will suggest completions like `.capitalize`. The completion power of IPython
53 will increase with new Jedi releases, and a number of bugs and more completions
54 are already available on development version of :any:`jedi` if you are curious.
55
56 With the help of prompt toolkit, types of completions can be shown in the
57 completer interface:
58
59 .. image:: ../_images/jedi_type_inference_60.png
60 :alt: Jedi showing ability to do type inference
61 :align: center
62 :width: 400px
63 :target: ../_images/jedi_type_inference_60.png
64
65 The appearance of the completer is controlled by the
66 ``c.TerminalInteractiveShell.display_completions`` option that will show the
67 type differently depending on the value among ``'column'``, ``'multicolumn'``
68 and ``'readlinelike'``
69
70 The use of Jedi also full fill a number of request and fix a number of bugs
71 like case insensitive completion, completion after division operator: See
72 :ghpull:`10182`.
73
74 Extra patches and updates will be needed to the :mod:`ipykernel` package for
75 this feature to be available to other clients like jupyter Notebook, Lab,
76 Nteract, Hydrogen...
77
78 The use of Jedi can is barely noticeable on recent enough machines, but can be
79 feel on older ones, in cases were Jedi behavior need to be adjusted, the amount
80 of time given to Jedi to compute type inference can be adjusted with
81 ``c.IPCompleter.jedi_compute_type_timeout``, with object whose type were not
82 inferred will be shown as ``<unknown>``. Jedi can also be completely deactivated
83 by using the ``c.Completer.use_jedi=False`` option.
84
85
86 The old ``Completer.complete()`` API is waiting deprecation and should be
87 replaced replaced by ``Completer.completions()`` in a near future. Feedback on
88 the current state of the API and suggestions welcome.
89
90 Python 3 only codebase
91 ----------------------
92
93 One of the large challenges in IPython 6.0 has been the adoption of a pure
94 Python 3 code base, which lead us to great length to upstream patches in pip,
95 pypi and warehouse to make sure Python 2 system still upgrade to the latest
96 compatible Python version compatible.
97
98 We remind our Python 2 users that IPython 5 is still compatible with Python 2.7,
99 still maintained and get regular releases. Using pip 9+, upgrading IPython will
100 automatically upgrade to the latest version compatible with your system.
101
102 .. warning::
103
104 If you are on a system using an older verison of pip on Python 2, pip may
105 still install IPython 6.0 on your system, and IPython will refuse to start.
106 You can fix this by ugrading pip, and reinstalling ipython, or forcing pip to
107 install an earlier version: ``pip install 'ipython<6'``
108
109 The ability to use only Python 3 on the code base of IPython has bring a number
110 of advantage. Most of the newly written code make use of `optional function type
111 anotation <https://www.python.org/dev/peps/pep-0484/>`_ leading to clearer code
112 and better documentation.
113
114 The total size of the repository has also for a first time between releases
115 (excluding the big split for 4.0) decreased by about 1500 lines, potentially
116 quite a bit more codewide as some documents like this one are append only and
117 are about 300 lines long.
118
119 The removal as of Python2/Python3 shim layer has made the code quite clearer and
120 more idiomatic in a number of location, and much friendlier to work with and
121 understand. We hope to further embrace Python 3 capability in the next release
122 cycle and introduce more of the Python 3 only idioms (yield from, kwarg only,
123 general unpacking) in the code base of IPython, and see if we can take advantage
124 of these as well to improve user experience with better error messages and
125 hints.
126
127
128 Miscs improvements
129 ------------------
130
131
132 - The :cellmagic:`capture` magic can now capture the result of a cell (from an
133 expression on the last line), as well as printed and displayed output.
134 :ghpull:`9851`.
135
136 - Pressing Ctrl-Z in the terminal debugger now suspends IPython, as it already
137 does in the main terminal prompt.
138
139 - autoreload can now reload ``Enum``. See :ghissue:`10232` and :ghpull:`10316`
140
141 - IPython.display has gained a :any:`GeoJSON <IPython.display.GeoJSON>` object.
142 :ghpull:`10288` and :ghpull:`10253`
14
143
15 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
144 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
16
145
@@ -201,6 +201,7 b' install_requires = ['
201
201
202 extras_require.update({
202 extras_require.update({
203 ':python_version == "3.4"': ['typing'],
203 ':python_version == "3.4"': ['typing'],
204 ':python_version >= "3.5"': ['trio', 'curio'],
204 ':sys_platform != "win32"': ['pexpect'],
205 ':sys_platform != "win32"': ['pexpect'],
205 ':sys_platform == "darwin"': ['appnope'],
206 ':sys_platform == "darwin"': ['appnope'],
206 ':sys_platform == "win32"': ['colorama'],
207 ':sys_platform == "win32"': ['colorama'],
General Comments 0
You need to be logged in to leave comments. Login now