##// END OF EJS Templates
Merge pull request #11265 from Carreau/async-repl-aug-2018...
Matthias Bussonnier -
r24534:0acd580f merge
parent child Browse files
Show More
@@ -0,0 +1,157 b''
1 """
2 Async helper function that are invalid syntax on Python 3.5 and below.
3
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.
7
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.
11 """
12
13
14 import ast
15 import sys
16 from textwrap import dedent, indent
17
18
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)
28
29 def __str__(self):
30 return 'asyncio'
31
32 _asyncio_runner = _AsyncIORunner()
33
34
35 def _curio_runner(coroutine):
36 """
37 handler for curio autoawait
38 """
39 import curio
40
41 return curio.run(coroutine)
42
43
44 def _trio_runner(async_fn):
45 import trio
46
47 async def loc(coro):
48 """
49 We need the dummy no-op async def to protect from
50 trio's internal. See https://github.com/python-trio/trio/issues/89
51 """
52 return await coro
53
54 return trio.run(loc, async_fn)
55
56
57 def _pseudo_sync_runner(coro):
58 """
59 A runner that does not really allow async execution, and just advance the coroutine.
60
61 See discussion in https://github.com/python-trio/trio/issues/608,
62
63 Credit to Nathaniel Smith
64
65 """
66 try:
67 coro.send(None)
68 except StopIteration as exc:
69 return exc.value
70 else:
71 # TODO: do not raise but return an execution result with the right info.
72 raise RuntimeError(
73 "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
74 )
75
76
77 def _asyncify(code: str) -> str:
78 """wrap code in async def definition.
79
80 And setup a bit of context to run it later.
81 """
82 res = dedent(
83 """
84 async def __wrapper__():
85 try:
86 {usercode}
87 finally:
88 locals()
89 """
90 ).format(usercode=indent(code, " " * 8))
91 return res
92
93
94 class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):
95 """
96 Find syntax errors that would be an error in an async repl, but because
97 the implementation involves wrapping the repl in an async function, it
98 is erroneously allowed (e.g. yield or return at the top level)
99 """
100
101 def generic_visit(self, node):
102 func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
103 invalid_types = (ast.Return, ast.Yield, ast.YieldFrom)
104
105 if isinstance(node, func_types):
106 return # Don't recurse into functions
107 elif isinstance(node, invalid_types):
108 raise SyntaxError()
109 else:
110 super().generic_visit(node)
111
112
113 def _async_parse_cell(cell: str) -> ast.AST:
114 """
115 This is a compatibility shim for pre-3.7 when async outside of a function
116 is a syntax error at the parse stage.
117
118 It will return an abstract syntax tree parsed as if async and await outside
119 of a function were not a syntax error.
120 """
121 if sys.version_info < (3, 7):
122 # Prior to 3.7 you need to asyncify before parse
123 wrapped_parse_tree = ast.parse(_asyncify(cell))
124 return wrapped_parse_tree.body[0].body[0]
125 else:
126 return ast.parse(cell)
127
128
129 def _should_be_async(cell: str) -> bool:
130 """Detect if a block of code need to be wrapped in an `async def`
131
132 Attempt to parse the block of code, it it compile we're fine.
133 Otherwise we wrap if and try to compile.
134
135 If it works, assume it should be async. Otherwise Return False.
136
137 Not handled yet: If the block of code has a return statement as the top
138 level, it will be seen as async. This is a know limitation.
139 """
140
141 try:
142 # we can't limit ourself to ast.parse, as it __accepts__ to parse on
143 # 3.7+, but just does not _compile_
144 compile(cell, "<>", "exec")
145 return False
146 except SyntaxError:
147 try:
148 parse_tree = _async_parse_cell(cell)
149
150 # Raise a SyntaxError if there are top-level return or yields
151 v = _AsyncSyntaxErrorVisitor()
152 v.visit(parse_tree)
153
154 except SyntaxError:
155 return False
156 return True
157 return False
@@ -0,0 +1,277 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 from itertools import chain, repeat
9 import nose.tools as nt
10 from textwrap import dedent, indent
11 from unittest import TestCase
12 from IPython.testing.decorators import skip_without
13
14 ip = get_ipython()
15 iprc = lambda x: ip.run_cell(dedent(x)).raise_error()
16 iprc_nr = lambda x: ip.run_cell(dedent(x))
17
18 if sys.version_info > (3, 5):
19 from IPython.core.async_helpers import _should_be_async
20
21 class AsyncTest(TestCase):
22 def test_should_be_async(self):
23 nt.assert_false(_should_be_async("False"))
24 nt.assert_true(_should_be_async("await bar()"))
25 nt.assert_true(_should_be_async("x = await bar()"))
26 nt.assert_false(
27 _should_be_async(
28 dedent(
29 """
30 async def awaitable():
31 pass
32 """
33 )
34 )
35 )
36
37 def _get_top_level_cases(self):
38 # These are test cases that should be valid in a function
39 # but invalid outside of a function.
40 test_cases = []
41 test_cases.append(('basic', "{val}"))
42
43 # Note, in all conditional cases, I use True instead of
44 # False so that the peephole optimizer won't optimize away
45 # the return, so CPython will see this as a syntax error:
46 #
47 # while True:
48 # break
49 # return
50 #
51 # But not this:
52 #
53 # while False:
54 # return
55 #
56 # See https://bugs.python.org/issue1875
57
58 test_cases.append(('if', dedent("""
59 if True:
60 {val}
61 """)))
62
63 test_cases.append(('while', dedent("""
64 while True:
65 {val}
66 break
67 """)))
68
69 test_cases.append(('try', dedent("""
70 try:
71 {val}
72 except:
73 pass
74 """)))
75
76 test_cases.append(('except', dedent("""
77 try:
78 pass
79 except:
80 {val}
81 """)))
82
83 test_cases.append(('finally', dedent("""
84 try:
85 pass
86 except:
87 pass
88 finally:
89 {val}
90 """)))
91
92 test_cases.append(('for', dedent("""
93 for _ in range(4):
94 {val}
95 """)))
96
97
98 test_cases.append(('nested', dedent("""
99 if True:
100 while True:
101 {val}
102 break
103 """)))
104
105 test_cases.append(('deep-nested', dedent("""
106 if True:
107 while True:
108 break
109 for x in range(3):
110 if True:
111 while True:
112 for x in range(3):
113 {val}
114 """)))
115
116 return test_cases
117
118 def _get_ry_syntax_errors(self):
119 # This is a mix of tests that should be a syntax error if
120 # return or yield whether or not they are in a function
121
122 test_cases = []
123
124 test_cases.append(('class', dedent("""
125 class V:
126 {val}
127 """)))
128
129 test_cases.append(('nested-class', dedent("""
130 class V:
131 class C:
132 {val}
133 """)))
134
135 return test_cases
136
137
138 def test_top_level_return_error(self):
139 tl_err_test_cases = self._get_top_level_cases()
140 tl_err_test_cases.extend(self._get_ry_syntax_errors())
141
142 vals = ('return', 'yield', 'yield from (_ for _ in range(3))')
143
144 for test_name, test_case in tl_err_test_cases:
145 # This example should work if 'pass' is used as the value
146 with self.subTest((test_name, 'pass')):
147 iprc(test_case.format(val='pass'))
148
149 # It should fail with all the values
150 for val in vals:
151 with self.subTest((test_name, val)):
152 msg = "Syntax error not raised for %s, %s" % (test_name, val)
153 with self.assertRaises(SyntaxError, msg=msg):
154 iprc(test_case.format(val=val))
155
156 def test_in_func_no_error(self):
157 # Test that the implementation of top-level return/yield
158 # detection isn't *too* aggressive, and works inside a function
159 func_contexts = []
160
161 func_contexts.append(('func', False, dedent("""
162 def f():""")))
163
164 func_contexts.append(('method', False, dedent("""
165 class MyClass:
166 def __init__(self):
167 """)))
168
169 func_contexts.append(('async-func', True, dedent("""
170 async def f():""")))
171
172 func_contexts.append(('async-method', True, dedent("""
173 class MyClass:
174 async def f(self):""")))
175
176 func_contexts.append(('closure', False, dedent("""
177 def f():
178 def g():
179 """)))
180
181 def nest_case(context, case):
182 # Detect indentation
183 lines = context.strip().splitlines()
184 prefix_len = 0
185 for c in lines[-1]:
186 if c != ' ':
187 break
188 prefix_len += 1
189
190 indented_case = indent(case, ' ' * (prefix_len + 4))
191 return context + '\n' + indented_case
192
193 # Gather and run the tests
194
195 # yield is allowed in async functions, starting in Python 3.6,
196 # and yield from is not allowed in any version
197 vals = ('return', 'yield', 'yield from (_ for _ in range(3))')
198 async_safe = (True,
199 sys.version_info >= (3, 6),
200 False)
201 vals = tuple(zip(vals, async_safe))
202
203 success_tests = zip(self._get_top_level_cases(), repeat(False))
204 failure_tests = zip(self._get_ry_syntax_errors(), repeat(True))
205
206 tests = chain(success_tests, failure_tests)
207
208 for context_name, async_func, context in func_contexts:
209 for (test_name, test_case), should_fail in tests:
210 nested_case = nest_case(context, test_case)
211
212 for val, async_safe in vals:
213 val_should_fail = (should_fail or
214 (async_func and not async_safe))
215
216 test_id = (context_name, test_name, val)
217 cell = nested_case.format(val=val)
218
219 with self.subTest(test_id):
220 if val_should_fail:
221 msg = ("SyntaxError not raised for %s" %
222 str(test_id))
223 with self.assertRaises(SyntaxError, msg=msg):
224 iprc(cell)
225
226 print(cell)
227 else:
228 iprc(cell)
229
230
231 def test_execute(self):
232 iprc("""
233 import asyncio
234 await asyncio.sleep(0.001)
235 """
236 )
237
238 def test_autoawait(self):
239 iprc("%autoawait False")
240 iprc("%autoawait True")
241 iprc("""
242 from asyncio import sleep
243 await sleep(0.1)
244 """
245 )
246
247 @skip_without('curio')
248 def test_autoawait_curio(self):
249 iprc("%autoawait curio")
250
251 @skip_without('trio')
252 def test_autoawait_trio(self):
253 iprc("%autoawait trio")
254
255 @skip_without('trio')
256 def test_autoawait_trio_wrong_sleep(self):
257 iprc("%autoawait trio")
258 res = iprc_nr("""
259 import asyncio
260 await asyncio.sleep(0)
261 """)
262 with nt.assert_raises(TypeError):
263 res.raise_error()
264
265 @skip_without('trio')
266 def test_autoawait_asyncio_wrong_sleep(self):
267 iprc("%autoawait asyncio")
268 res = iprc_nr("""
269 import trio
270 await trio.sleep(0)
271 """)
272 with nt.assert_raises(RuntimeError):
273 res.raise_error()
274
275
276 def tearDown(self):
277 ip.loop_runner = "asyncio"
@@ -0,0 +1,222 b''
1 .. _autoawait:
2
3 Asynchronous in REPL: Autoawait
4 ===============================
5
6 .. note::
7
8 This feature is experimental and behavior can change betwen python and
9 IPython version without prior deprecation.
10
11 Starting with IPython 7.0, and when user Python 3.6 and above, IPython offer the
12 ability to run asynchronous code from the REPL. Constructs which are
13 :exc:`SyntaxError` s in the Python REPL can be used seamlessly in IPython.
14
15 The example given here are for terminal IPython, running async code in a
16 notebook interface or any other frontend using the Jupyter protocol will need to
17 use a newer version of IPykernel. The details of how async code runs in
18 IPykernel will differ between IPython, IPykernel and their versions.
19
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
24 <https://docs.python.org/3/reference/compound_stmts.html#async-def>`_ function
25 context are present. For example, the following being a syntax error in the
26 Python REPL::
27
28 Python 3.6.0
29 [GCC 4.2.1]
30 Type "help", "copyright", "credits" or "license" for more information.
31 >>> import aiohttp
32 >>> result = aiohttp.get('https://api.github.com')
33 >>> response = await result
34 File "<stdin>", line 1
35 response = await result
36 ^
37 SyntaxError: invalid syntax
38
39 Should behave as expected in the IPython REPL::
40
41 Python 3.6.0
42 Type 'copyright', 'credits' or 'license' for more information
43 IPython 7.0.0 -- An enhanced Interactive Python. Type '?' for help.
44
45 In [1]: import aiohttp
46 ...: result = aiohttp.get('https://api.github.com')
47
48 In [2]: response = await result
49 <pause for a few 100s ms>
50
51 In [3]: await response.json()
52 Out[3]:
53 {'authorizations_url': 'https://api.github.com/authorizations',
54 'code_search_url': 'https://api.github.com/search/code?q={query}...',
55 ...
56 }
57
58
59 You can use the ``c.InteractiveShell.autoawait`` configuration option and set it
60 to :any:`False` to deactivate automatic wrapping of asynchronous code. You can also
61 use the :magic:`%autoawait` magic to toggle the behavior at runtime::
62
63 In [1]: %autoawait False
64
65 In [2]: %autoawait
66 IPython autoawait is `Off`, and set to use `asyncio`
67
68
69
70 By default IPython will assume integration with Python's provided
71 :mod:`asyncio`, but integration with other libraries is provided. In particular
72 we provide experimental integration with the ``curio`` and ``trio`` library.
73
74 You can switch current integration by using the
75 ``c.InteractiveShell.loop_runner`` option or the ``autoawait <name
76 integration>`` magic.
77
78 For example::
79
80 In [1]: %autoawait trio
81
82 In [2]: import trio
83
84 In [3]: async def child(i):
85 ...: print(" child %s goes to sleep"%i)
86 ...: await trio.sleep(2)
87 ...: print(" child %s wakes up"%i)
88
89 In [4]: print('parent start')
90 ...: async with trio.open_nursery() as n:
91 ...: for i in range(5):
92 ...: n.spawn(child, i)
93 ...: print('parent end')
94 parent start
95 child 2 goes to sleep
96 child 0 goes to sleep
97 child 3 goes to sleep
98 child 1 goes to sleep
99 child 4 goes to sleep
100 <about 2 seconds pause>
101 child 2 wakes up
102 child 1 wakes up
103 child 0 wakes up
104 child 3 wakes up
105 child 4 wakes up
106 parent end
107
108
109 In the above example, ``async with`` at top level scope is a syntax error in
110 Python.
111
112 Using this mode can have unexpected consequences if used in interaction with
113 other features of IPython and various registered extensions. In particular if you
114 are a direct or indirect user of the AST transformers, these may not apply to
115 your code.
116
117 When using command line IPython, the default loop (or runner) does not process
118 in the background, so top level asynchronous code must finish for the REPL to
119 allow you to enter more code. As with usual Python semantic, the awaitables are
120 started only when awaited for the first time. That is to say, in first example,
121 no network request is done between ``In[1]`` and ``In[2]``.
122
123
124 Effects on IPython.embed()
125 ==========================
126
127 IPython core being asynchronous, the use of ``IPython.embed()`` will now require
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.
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.
134
135 Effects on Magics
136 =================
137
138 A couple of magics (``%%timeit``, ``%timeit``, ``%%time``, ``%%prun``) have not
139 yet been updated to work with asynchronous code and will raise syntax errors
140 when trying to use top-level ``await``. We welcome any contribution to help fix
141 those, and extra cases we haven't caught yet. We hope for better support in Cor
142 Python for top-level Async code.
143
144 Internals
145 =========
146
147 As running asynchronous code is not supported in interactive REPL (as of Python
148 3.7) we have to rely to a number of complex workaround and heuristic to allow
149 this to happen. It is interesting to understand how this works in order to
150 comprehend potential bugs, or provide a custom runner.
151
152 Among the many approaches that are at our disposition, we find only one that
153 suited out need. Under the hood we use the code object from a async-def function
154 and run it in global namespace after modifying it to not create a new
155 ``locals()`` scope::
156
157 async def inner_async():
158 locals().update(**global_namespace)
159 #
160 # here is user code
161 #
162 return last_user_statement
163 codeobj = modify(inner_async.__code__)
164 coroutine = eval(codeobj, user_ns)
165 display(loop_runner(coroutine))
166
167
168
169 The first thing you'll notice is that unlike classical ``exec``, there is only
170 one namespace. Second, user code runs in a function scope, and not a module
171 scope.
172
173 On top of the above there are significant modification to the AST of
174 ``function``, and ``loop_runner`` can be arbitrary complex. So there is a
175 significant overhead to this kind of code.
176
177 By default the generated coroutine function will be consumed by Asyncio's
178 ``loop_runner = asyncio.get_evenloop().run_until_complete()`` method if
179 ``async`` mode is deemed necessary, otherwise the coroutine will just be
180 exhausted in a simple runner. It is though possible to change the default
181 runner.
182
183 A loop runner is a *synchronous* function responsible from running a coroutine
184 object.
185
186 The runner is responsible from ensuring that ``coroutine`` run to completion,
187 and should return the result of executing the coroutine. Let's write a
188 runner for ``trio`` that print a message when used as an exercise, ``trio`` is
189 special as it usually prefer to run a function object and make a coroutine by
190 itself, we can get around this limitation by wrapping it in an async-def without
191 parameters and passing this value to ``trio``::
192
193
194 In [1]: import trio
195 ...: from types import CoroutineType
196 ...:
197 ...: def trio_runner(coro:CoroutineType):
198 ...: print('running asynchronous code')
199 ...: async def corowrap(coro):
200 ...: return await coro
201 ...: return trio.run(corowrap, coro)
202
203 We can set it up by passing it to ``%autoawait``::
204
205 In [2]: %autoawait trio_runner
206
207 In [3]: async def async_hello(name):
208 ...: await trio.sleep(1)
209 ...: print(f'Hello {name} world !')
210 ...: await trio.sleep(1)
211
212 In [4]: await async_hello('async')
213 running asynchronous code
214 Hello async world !
215
216
217 Asynchronous programming in python (and in particular in the REPL) is still a
218 relatively young subject. We expect some code to not behave as you expect, so
219 feel free to contribute improvements to this codebase and give us feedback.
220
221 We invite you to thoroughly test this feature and report any unexpected behavior
222 as well as propose any improvement.
@@ -0,0 +1,99 b''
1 Autowait: Asynchronous REPL
2 ---------------------------
3
4 Staring with IPython 7.0 and on Python 3.6+, IPython can automatically await
5 code at top level, you should not need to access an event loop or runner
6 yourself. To know more read the :ref:`autoawait` section of our docs, see
7 :ghpull:`11265` or try the following code::
8
9 Python 3.6.0
10 Type 'copyright', 'credits' or 'license' for more information
11 IPython 7.0.0 -- An enhanced Interactive Python. Type '?' for help.
12
13 In [1]: import aiohttp
14 ...: result = aiohttp.get('https://api.github.com')
15
16 In [2]: response = await result
17 <pause for a few 100s ms>
18
19 In [3]: await response.json()
20 Out[3]:
21 {'authorizations_url': 'https://api.github.com/authorizations',
22 'code_search_url': 'https://api.github.com/search/code?q={query}{&page,per_page,sort,order}',
23 ...
24 }
25
26 .. note::
27
28 Async integration is experimental code, behavior may change or be removed
29 between Python and IPython versions without warnings.
30
31 Integration is by default with `asyncio`, but other libraries can be configured,
32 like ``curio`` or ``trio``, to improve concurrency in the REPL::
33
34 In [1]: %autoawait trio
35
36 In [2]: import trio
37
38 In [3]: async def child(i):
39 ...: print(" child %s goes to sleep"%i)
40 ...: await trio.sleep(2)
41 ...: print(" child %s wakes up"%i)
42
43 In [4]: print('parent start')
44 ...: async with trio.open_nursery() as n:
45 ...: for i in range(3):
46 ...: n.spawn(child, i)
47 ...: print('parent end')
48 parent start
49 child 2 goes to sleep
50 child 0 goes to sleep
51 child 1 goes to sleep
52 <about 2 seconds pause>
53 child 2 wakes up
54 child 1 wakes up
55 child 0 wakes up
56 parent end
57
58 See :ref:`autoawait` for more information.
59
60
61 Asynchronous code in a Notebook interface or any other frontend using the
62 Jupyter Protocol will need further updates of the IPykernel package.
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.
72
73 Change to Nested Embed
74 ----------------------
75
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 Effects on Magics
81 -----------------
82
83 Some magics will not work with Async, and will need updates. Contribution
84 welcome.
85
86 Expected Future changes
87 -----------------------
88
89 We expect more internal but public IPython function to become ``async``, and
90 will likely end up having a persisting event loop while IPython is running.
91
92 Thanks
93 ------
94
95 This took more than a year in the making, and the code was rebased a number of
96 time leading to commit authorship that may have been lost in the final
97 Pull-Request. Huge thanks to many people for contribution, discussion, code,
98 documentation, use-case: dalejung, danielballan, ellisonbg, fperez, gnestor,
99 minrk, njsmith, pganssle, tacaswell, takluyver , vidartf ... And many other.
@@ -0,0 +1,43 b''
1 """
2 This tool is used during CI testing to make sure sphinx raise no error.
3
4 During development, we like to have whatsnew/pr/*.rst documents to track
5 individual new features. Unfortunately they other either:
6 - have no title (sphinx complains)
7 - are not included in any toctree (sphinx complain)
8
9 This fix-them up by "inventing" a title, before building the docs. At release
10 time, these title and files will anyway be rewritten into the actual release
11 notes.
12 """
13
14 import glob
15
16
17 def main():
18 folder = 'docs/source/whatsnew/pr/'
19 assert folder.endswith('/')
20 files = glob.glob(folder+'*.rst')
21 print(files)
22
23 for filename in files:
24 print('Adding pseudo-title to:', filename)
25 title = filename[:-4].split('/')[-1].replace('-', ' ').capitalize()
26
27 with open(filename) as f:
28 data = f.read()
29 if data and data.splitlines()[1].startswith('='):
30 continue
31
32 with open(filename, 'w') as f:
33 f.write(title+'\n')
34 f.write('='* len(title)+'\n\n')
35 f.write(data)
36
37 if __name__ == '__main__':
38 main()
39
40
41
42
43
@@ -5,7 +5,6 b' python:'
5 - "3.7-dev"
5 - "3.7-dev"
6 - 3.6
6 - 3.6
7 - 3.5
7 - 3.5
8 - 3.4
9 sudo: false
8 sudo: false
10 env:
9 env:
11 global:
10 global:
@@ -17,6 +16,7 b' install:'
17 - pip install pip --upgrade
16 - pip install pip --upgrade
18 - pip install setuptools --upgrade
17 - pip install setuptools --upgrade
19 - pip install -e file://$PWD#egg=ipython[test] --upgrade
18 - pip install -e file://$PWD#egg=ipython[test] --upgrade
19 - pip install trio curio
20 - pip install codecov check-manifest --upgrade
20 - pip install codecov check-manifest --upgrade
21 - sudo apt-get install graphviz
21 - sudo apt-get install graphviz
22 script:
22 script:
@@ -26,6 +26,7 b' script:'
26 - |
26 - |
27 if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then
27 if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then
28 pip install -r docs/requirements.txt
28 pip install -r docs/requirements.txt
29 python tools/fixup_whats_new_pr.py
29 make -C docs/ html SPHINXOPTS="-W"
30 make -C docs/ html SPHINXOPTS="-W"
30 fi
31 fi
31 after_success:
32 after_success:
@@ -27,12 +27,12 b' import sys'
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 # Don't forget to also update setup.py when this changes!
29 # Don't forget to also update setup.py when this changes!
30 if sys.version_info < (3,4):
30 if sys.version_info < (3, 5):
31 raise ImportError(
31 raise ImportError(
32 """
32 """
33 IPython 7.0+ supports Python 3.4 and above.
33 IPython 7.0+ supports Python 3.5 and above.
34 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
34 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
35 Python 3.3 was supported up to IPython 6.x.
35 Python 3.3 and 3.4 were supported up to IPython 6.x.
36
36
37 See IPython `README.rst` file for more information:
37 See IPython `README.rst` file for more information:
38
38
@@ -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,104 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 # we still need to run things using the asyncio eventloop, but there is no
154 # async integration
155 from .async_helpers import (_asyncio_runner, _asyncify, _pseudo_sync_runner)
156
157 if sys.version_info > (3, 5):
158 from .async_helpers import _curio_runner, _trio_runner, _should_be_async
159 else :
160 _curio_runner = _trio_runner = None
161
162 def _should_be_async(cell:str)->bool:
163 return False
164
165
166 def _ast_asyncify(cell:str, wrapper_name:str) -> ast.Module:
167 """
168 Parse a cell with top-level await and modify the AST to be able to run it later.
169
170 Parameter
171 ---------
172
173 cell: str
174 The code cell to asyncronify
175 wrapper_name: str
176 The name of the function to be used to wrap the passed `cell`. It is
177 advised to **not** use a python identifier in order to not pollute the
178 global namespace in which the function will be ran.
179
180 Return
181 ------
182
183 A module object AST containing **one** function named `wrapper_name`.
184
185 The given code is wrapped in a async-def function, parsed into an AST, and
186 the resulting function definition AST is modified to return the last
187 expression.
188
189 The last expression or await node is moved into a return statement at the
190 end of the function, and removed from its original location. If the last
191 node is not Expr or Await nothing is done.
192
193 The function `__code__` will need to be later modified (by
194 ``removed_co_newlocals``) in a subsequent step to not create new `locals()`
195 meaning that the local and global scope are the same, ie as if the body of
196 the function was at module level.
197
198 Lastly a call to `locals()` is made just before the last expression of the
199 function, or just after the last assignment or statement to make sure the
200 global dict is updated as python function work with a local fast cache which
201 is updated only on `local()` calls.
202 """
203
204 from ast import Expr, Await, Return
205 tree = ast.parse(_asyncify(cell))
206
207 function_def = tree.body[0]
208 function_def.name = wrapper_name
209 try_block = function_def.body[0]
210 lastexpr = try_block.body[-1]
211 if isinstance(lastexpr, (Expr, Await)):
212 try_block.body[-1] = Return(lastexpr.value)
213 ast.fix_missing_locations(tree)
214 return tree
215 #-----------------------------------------------------------------------------
116 # Globals
216 # Globals
117 #-----------------------------------------------------------------------------
217 #-----------------------------------------------------------------------------
118
218
@@ -257,6 +357,43 b' class InteractiveShell(SingletonConfigurable):'
257 """
357 """
258 ).tag(config=True)
358 ).tag(config=True)
259
359
360 autoawait = Bool(True, help=
361 """
362 Automatically run await statement in the top level repl.
363 """
364 ).tag(config=True)
365
366 loop_runner_map ={
367 'asyncio':(_asyncio_runner, True),
368 'curio':(_curio_runner, True),
369 'trio':(_trio_runner, True),
370 'sync': (_pseudo_sync_runner, False)
371 }
372
373 loop_runner = Any(default_value="IPython.core.interactiveshell._asyncio_runner",
374 allow_none=True,
375 help="""Select the loop runner that will be used to execute top-level asynchronous code"""
376 ).tag(config=True)
377
378 @default('loop_runner')
379 def _default_loop_runner(self):
380 return import_item("IPython.core.interactiveshell._asyncio_runner")
381
382 @validate('loop_runner')
383 def _import_runner(self, proposal):
384 if isinstance(proposal.value, str):
385 if proposal.value in self.loop_runner_map:
386 runner, autoawait = self.loop_runner_map[proposal.value]
387 self.autoawait = autoawait
388 return runner
389 runner = import_item(proposal.value)
390 if not callable(runner):
391 raise ValueError('loop_runner must be callable')
392 return runner
393 if not callable(proposal.value):
394 raise ValueError('loop_runner must be callable')
395 return proposal.value
396
260 automagic = Bool(True, help=
397 automagic = Bool(True, help=
261 """
398 """
262 Enable magic commands to be called without the leading %.
399 Enable magic commands to be called without the leading %.
@@ -1464,6 +1601,7 b' class InteractiveShell(SingletonConfigurable):'
1464 parent = None
1601 parent = None
1465 obj = None
1602 obj = None
1466
1603
1604
1467 # Look for the given name by splitting it in parts. If the head is
1605 # Look for the given name by splitting it in parts. If the head is
1468 # found, then we look for all the remaining parts as members, and only
1606 # found, then we look for all the remaining parts as members, and only
1469 # declare success if we can find them all.
1607 # declare success if we can find them all.
@@ -1999,7 +2137,6 b' class InteractiveShell(SingletonConfigurable):'
1999 self.set_hook('complete_command', cd_completer, str_key = '%cd')
2137 self.set_hook('complete_command', cd_completer, str_key = '%cd')
2000 self.set_hook('complete_command', reset_completer, str_key = '%reset')
2138 self.set_hook('complete_command', reset_completer, str_key = '%reset')
2001
2139
2002
2003 @skip_doctest
2140 @skip_doctest
2004 def complete(self, text, line=None, cursor_pos=None):
2141 def complete(self, text, line=None, cursor_pos=None):
2005 """Return the completed text and a list of completions.
2142 """Return the completed text and a list of completions.
@@ -2083,6 +2220,8 b' class InteractiveShell(SingletonConfigurable):'
2083 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2220 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2084 m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics,
2221 m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics,
2085 )
2222 )
2223 if sys.version_info >(3,5):
2224 self.register_magics(m.AsyncMagics)
2086
2225
2087 # Register Magic Aliases
2226 # Register Magic Aliases
2088 mman = self.magics_manager
2227 mman = self.magics_manager
@@ -2682,15 +2821,59 b' class InteractiveShell(SingletonConfigurable):'
2682 self.events.trigger('post_run_cell', result)
2821 self.events.trigger('post_run_cell', result)
2683 return result
2822 return result
2684
2823
2685 def _run_cell(self, raw_cell, store_history, silent, shell_futures):
2824 def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures:bool):
2686 """Internal method to run a complete IPython cell.
2825 """Internal method to run a complete IPython cell."""
2826 coro = self.run_cell_async(
2827 raw_cell,
2828 store_history=store_history,
2829 silent=silent,
2830 shell_futures=shell_futures,
2831 )
2832
2833 # run_cell_async is async, but may not actually need and eventloop.
2834 # when this is the case, we want to run it using the pseudo_sync_runner
2835 # so that code can invoke eventloops (for example via the %run , and
2836 # `%paste` magic.
2837 try:
2838 interactivity = coro.send(None)
2839 except StopIteration as exc:
2840 return exc.value
2841
2842 # if code was not async, sending `None` was actually executing the code.
2843 if isinstance(interactivity, ExecutionResult):
2844 return interactivity
2845
2846 if interactivity == 'async':
2847 try:
2848 return self.loop_runner(coro)
2849 except Exception as e:
2850 info = ExecutionInfo(raw_cell, store_history, silent, shell_futures)
2851 result = ExecutionResult(info)
2852 result.error_in_exec = e
2853 self.showtraceback(running_compiled_code=True)
2854 return result
2855 return _pseudo_sync_runner(coro)
2856
2857 @asyncio.coroutine
2858 def run_cell_async(self, raw_cell:str, store_history=False, silent=False, shell_futures=True) -> ExecutionResult:
2859 """Run a complete IPython cell asynchronously.
2687
2860
2688 Parameters
2861 Parameters
2689 ----------
2862 ----------
2690 raw_cell : str
2863 raw_cell : str
2864 The code (including IPython code such as %magic functions) to run.
2691 store_history : bool
2865 store_history : bool
2866 If True, the raw and translated cell will be stored in IPython's
2867 history. For user code calling back into IPython's machinery, this
2868 should be set to False.
2692 silent : bool
2869 silent : bool
2870 If True, avoid side-effects, such as implicit displayhooks and
2871 and logging. silent=True forces store_history=False.
2693 shell_futures : bool
2872 shell_futures : bool
2873 If True, the code will share future statements with the interactive
2874 shell. It will both be affected by previous __future__ imports, and
2875 any __future__ imports in the code will affect the shell. If False,
2876 __future__ imports are not shared in either direction.
2694
2877
2695 Returns
2878 Returns
2696 -------
2879 -------
@@ -2754,13 +2937,33 b' class InteractiveShell(SingletonConfigurable):'
2754 # compiler
2937 # compiler
2755 compiler = self.compile if shell_futures else CachingCompiler()
2938 compiler = self.compile if shell_futures else CachingCompiler()
2756
2939
2940 _run_async = False
2941
2757 with self.builtin_trap:
2942 with self.builtin_trap:
2758 cell_name = self.compile.cache(cell, self.execution_count)
2943 cell_name = self.compile.cache(cell, self.execution_count)
2759
2944
2760 with self.display_trap:
2945 with self.display_trap:
2761 # Compile to bytecode
2946 # Compile to bytecode
2762 try:
2947 try:
2763 code_ast = compiler.ast_parse(cell, filename=cell_name)
2948 if self.autoawait and _should_be_async(cell):
2949 # the code AST below will not be user code: we wrap it
2950 # in an `async def`. This will likely make some AST
2951 # transformer below miss some transform opportunity and
2952 # introduce a small coupling to run_code (in which we
2953 # bake some assumptions of what _ast_asyncify returns.
2954 # they are ways around (like grafting part of the ast
2955 # later:
2956 # - Here, return code_ast.body[0].body[1:-1], as well
2957 # as last expression in return statement which is
2958 # the user code part.
2959 # - Let it go through the AST transformers, and graft
2960 # - it back after the AST transform
2961 # But that seem unreasonable, at least while we
2962 # do not need it.
2963 code_ast = _ast_asyncify(cell, 'async-def-wrapper')
2964 _run_async = True
2965 else:
2966 code_ast = compiler.ast_parse(cell, filename=cell_name)
2764 except self.custom_exceptions as e:
2967 except self.custom_exceptions as e:
2765 etype, value, tb = sys.exc_info()
2968 etype, value, tb = sys.exc_info()
2766 self.CustomTB(etype, value, tb)
2969 self.CustomTB(etype, value, tb)
@@ -2785,9 +2988,14 b' class InteractiveShell(SingletonConfigurable):'
2785 self.displayhook.exec_result = result
2988 self.displayhook.exec_result = result
2786
2989
2787 # Execute the user code
2990 # Execute the user code
2788 interactivity = 'none' if silent else self.ast_node_interactivity
2991 interactivity = "none" if silent else self.ast_node_interactivity
2789 has_raised = self.run_ast_nodes(code_ast.body, cell_name,
2992 if _run_async:
2790 interactivity=interactivity, compiler=compiler, result=result)
2993 interactivity = 'async'
2994 # yield interactivity so let run_cell decide whether to use
2995 # an async loop_runner
2996 yield interactivity
2997 has_raised = yield from self.run_ast_nodes(code_ast.body, cell_name,
2998 interactivity=interactivity, compiler=compiler, result=result)
2791
2999
2792 self.last_execution_succeeded = not has_raised
3000 self.last_execution_succeeded = not has_raised
2793 self.last_execution_result = result
3001 self.last_execution_result = result
@@ -2861,12 +3069,12 b' class InteractiveShell(SingletonConfigurable):'
2861 except Exception:
3069 except Exception:
2862 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
3070 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
2863 self.ast_transformers.remove(transformer)
3071 self.ast_transformers.remove(transformer)
2864
3072
2865 if self.ast_transformers:
3073 if self.ast_transformers:
2866 ast.fix_missing_locations(node)
3074 ast.fix_missing_locations(node)
2867 return node
3075 return node
2868
2869
3076
3077 @asyncio.coroutine
2870 def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='last_expr',
3078 def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='last_expr',
2871 compiler=compile, result=None):
3079 compiler=compile, result=None):
2872 """Run a sequence of AST nodes. The execution mode depends on the
3080 """Run a sequence of AST nodes. The execution mode depends on the
@@ -2887,6 +3095,12 b' class InteractiveShell(SingletonConfigurable):'
2887 are not displayed) 'last_expr_or_assign' will run the last expression
3095 are not displayed) 'last_expr_or_assign' will run the last expression
2888 or the last assignment. Other values for this parameter will raise a
3096 or the last assignment. Other values for this parameter will raise a
2889 ValueError.
3097 ValueError.
3098
3099 Experimental value: 'async' Will try to run top level interactive
3100 async/await code in default runner, this will not respect the
3101 interactivty setting and will only run the last node if it is an
3102 expression.
3103
2890 compiler : callable
3104 compiler : callable
2891 A function with the same interface as the built-in compile(), to turn
3105 A function with the same interface as the built-in compile(), to turn
2892 the AST nodes into code objects. Default is the built-in compile().
3106 the AST nodes into code objects. Default is the built-in compile().
@@ -2915,6 +3129,7 b' class InteractiveShell(SingletonConfigurable):'
2915 nodelist.append(nnode)
3129 nodelist.append(nnode)
2916 interactivity = 'last_expr'
3130 interactivity = 'last_expr'
2917
3131
3132 _async = False
2918 if interactivity == 'last_expr':
3133 if interactivity == 'last_expr':
2919 if isinstance(nodelist[-1], ast.Expr):
3134 if isinstance(nodelist[-1], ast.Expr):
2920 interactivity = "last"
3135 interactivity = "last"
@@ -2927,20 +3142,32 b' class InteractiveShell(SingletonConfigurable):'
2927 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
3142 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
2928 elif interactivity == 'all':
3143 elif interactivity == 'all':
2929 to_run_exec, to_run_interactive = [], nodelist
3144 to_run_exec, to_run_interactive = [], nodelist
3145 elif interactivity == 'async':
3146 _async = True
2930 else:
3147 else:
2931 raise ValueError("Interactivity was %r" % interactivity)
3148 raise ValueError("Interactivity was %r" % interactivity)
2932 try:
3149 try:
2933 for i, node in enumerate(to_run_exec):
3150 if _async:
2934 mod = ast.Module([node])
3151 # If interactivity is async the semantics of run_code are
2935 code = compiler(mod, cell_name, "exec")
3152 # completely different Skip usual machinery.
2936 if self.run_code(code, result):
3153 mod = ast.Module(nodelist)
2937 return True
3154 async_wrapper_code = compiler(mod, 'cell_name', 'exec')
2938
3155 exec(async_wrapper_code, self.user_global_ns, self.user_ns)
2939 for i, node in enumerate(to_run_interactive):
3156 async_code = removed_co_newlocals(self.user_ns.pop('async-def-wrapper')).__code__
2940 mod = ast.Interactive([node])
3157 if (yield from self.run_code(async_code, result, async_=True)):
2941 code = compiler(mod, cell_name, "single")
2942 if self.run_code(code, result):
2943 return True
3158 return True
3159 else:
3160 for i, node in enumerate(to_run_exec):
3161 mod = ast.Module([node])
3162 code = compiler(mod, cell_name, "exec")
3163 if (yield from self.run_code(code, result)):
3164 return True
3165
3166 for i, node in enumerate(to_run_interactive):
3167 mod = ast.Interactive([node])
3168 code = compiler(mod, cell_name, "single")
3169 if (yield from self.run_code(code, result)):
3170 return True
2944
3171
2945 # Flush softspace
3172 # Flush softspace
2946 if softspace(sys.stdout, 0):
3173 if softspace(sys.stdout, 0):
@@ -2963,7 +3190,23 b' class InteractiveShell(SingletonConfigurable):'
2963
3190
2964 return False
3191 return False
2965
3192
2966 def run_code(self, code_obj, result=None):
3193 def _async_exec(self, code_obj: types.CodeType, user_ns: dict):
3194 """
3195 Evaluate an asynchronous code object using a code runner
3196
3197 Fake asynchronous execution of code_object in a namespace via a proxy namespace.
3198
3199 Returns coroutine object, which can be executed via async loop runner
3200
3201 WARNING: The semantics of `async_exec` are quite different from `exec`,
3202 in particular you can only pass a single namespace. It also return a
3203 handle to the value of the last things returned by code_object.
3204 """
3205
3206 return eval(code_obj, user_ns)
3207
3208 @asyncio.coroutine
3209 def run_code(self, code_obj, result=None, *, async_=False):
2967 """Execute a code object.
3210 """Execute a code object.
2968
3211
2969 When an exception occurs, self.showtraceback() is called to display a
3212 When an exception occurs, self.showtraceback() is called to display a
@@ -2975,6 +3218,8 b' class InteractiveShell(SingletonConfigurable):'
2975 A compiled code object, to be executed
3218 A compiled code object, to be executed
2976 result : ExecutionResult, optional
3219 result : ExecutionResult, optional
2977 An object to store exceptions that occur during execution.
3220 An object to store exceptions that occur during execution.
3221 async_ : Bool (Experimental)
3222 Attempt to run top-level asynchronous code in a default loop.
2978
3223
2979 Returns
3224 Returns
2980 -------
3225 -------
@@ -2992,8 +3237,12 b' class InteractiveShell(SingletonConfigurable):'
2992 try:
3237 try:
2993 try:
3238 try:
2994 self.hooks.pre_run_code_hook()
3239 self.hooks.pre_run_code_hook()
2995 #rprint('Running code', repr(code_obj)) # dbg
3240 if async_:
2996 exec(code_obj, self.user_global_ns, self.user_ns)
3241 last_expr = (yield from self._async_exec(code_obj, self.user_ns))
3242 code = compile('last_expr', 'fake', "single")
3243 exec(code, {'last_expr': last_expr})
3244 else:
3245 exec(code_obj, self.user_global_ns, self.user_ns)
2997 finally:
3246 finally:
2998 # Reset our crash handler in place
3247 # Reset our crash handler in place
2999 sys.excepthook = old_excepthook
3248 sys.excepthook = old_excepthook
@@ -14,7 +14,7 b''
14
14
15 from ..magic import Magics, magics_class
15 from ..magic import Magics, magics_class
16 from .auto import AutoMagics
16 from .auto import AutoMagics
17 from .basic import BasicMagics
17 from .basic import BasicMagics, AsyncMagics
18 from .code import CodeMagics, MacroToEdit
18 from .code import CodeMagics, MacroToEdit
19 from .config import ConfigMagics
19 from .config import ConfigMagics
20 from .display import DisplayMagics
20 from .display import DisplayMagics
@@ -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):
@@ -378,6 +379,8 b' Currently the magic system has the following functions:""",'
378 except:
379 except:
379 xmode_switch_err('user')
380 xmode_switch_err('user')
380
381
382
383
381 @line_magic
384 @line_magic
382 def pip(self, args=''):
385 def pip(self, args=''):
383 """
386 """
@@ -597,3 +600,72 b' Currently the magic system has the following functions:""",'
597 nb = v4.new_notebook(cells=cells)
600 nb = v4.new_notebook(cells=cells)
598 with io.open(args.filename, 'w', encoding='utf-8') as f:
601 with io.open(args.filename, 'w', encoding='utf-8') as f:
599 write(nb, f, version=4)
602 write(nb, f, version=4)
603
604 @magics_class
605 class AsyncMagics(BasicMagics):
606
607 @line_magic
608 def autoawait(self, parameter_s):
609 """
610 Allow to change the status of the autoawait option.
611
612 This allow you to set a specific asynchronous code runner.
613
614 If no value is passed, print the currently used asynchronous integration
615 and whether it is activated.
616
617 It can take a number of value evaluated in the following order:
618
619 - False/false/off deactivate autoawait integration
620 - True/true/on activate autoawait integration using configured default
621 loop
622 - asyncio/curio/trio activate autoawait integration and use integration
623 with said library.
624
625 - `sync` turn on the pseudo-sync integration (mostly used for
626 `IPython.embed()` which does not run IPython with a real eventloop and
627 deactivate running asynchronous code. Turning on Asynchronous code with
628 the pseudo sync loop is undefined behavior and may lead IPython to crash.
629
630 If the passed parameter does not match any of the above and is a python
631 identifier, get said object from user namespace and set it as the
632 runner, and activate autoawait.
633
634 If the object is a fully qualified object name, attempt to import it and
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 """
641
642 param = parameter_s.strip()
643 d = {True: "on", False: "off"}
644
645 if not param:
646 print("IPython autoawait is `{}`, and set to use `{}`".format(
647 d[self.shell.autoawait],
648 self.shell.loop_runner
649 ))
650 return None
651
652 if param.lower() in ('false', 'off'):
653 self.shell.autoawait = False
654 return None
655 if param.lower() in ('true', 'on'):
656 self.shell.autoawait = True
657 return None
658
659 if param in self.shell.loop_runner_map:
660 self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
661 return None
662
663 if param in self.shell.user_ns :
664 self.shell.loop_runner = self.shell.user_ns[param]
665 self.shell.autoawait = True
666 return None
667
668 runner = import_item(param)
669
670 self.shell.loop_runner = runner
671 self.shell.autoawait = True
@@ -413,7 +413,7 b' def test_pretty_environ():'
413 # reindent to align with 'environ' prefix
413 # reindent to align with 'environ' prefix
414 dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ')))
414 dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ')))
415 env_repr = pretty.pretty(os.environ)
415 env_repr = pretty.pretty(os.environ)
416 nt.assert_equals(env_repr, 'environ' + dict_indented)
416 nt.assert_equal(env_repr, 'environ' + dict_indented)
417
417
418
418
419 def test_function_pretty():
419 def test_function_pretty():
@@ -366,6 +366,9 b' def embed(**kwargs):'
366 config = load_default_config()
366 config = load_default_config()
367 config.InteractiveShellEmbed = config.TerminalInteractiveShell
367 config.InteractiveShellEmbed = config.TerminalInteractiveShell
368 kwargs['config'] = config
368 kwargs['config'] = config
369 using = kwargs.get('using', 'sync')
370 if using :
371 kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor', 'autoawait': using!='sync'}})
369 #save ps1/ps2 if defined
372 #save ps1/ps2 if defined
370 ps1 = None
373 ps1 = None
371 ps2 = None
374 ps2 = None
@@ -383,7 +386,7 b' def embed(**kwargs):'
383 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
386 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
384 frame.f_code.co_filename, frame.f_lineno), **kwargs)
387 frame.f_code.co_filename, frame.f_lineno), **kwargs)
385 shell(header=header, stack_depth=2, compile_flags=compile_flags,
388 shell(header=header, stack_depth=2, compile_flags=compile_flags,
386 _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
389 _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
387 InteractiveShellEmbed.clear_instance()
390 InteractiveShellEmbed.clear_instance()
388 #restore previous instance
391 #restore previous instance
389 if saved_shell_instance is not None:
392 if saved_shell_instance is not None:
@@ -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)
@@ -8,10 +8,6 b' environment:'
8 PYTHON_VERSION: "3.6.x"
8 PYTHON_VERSION: "3.6.x"
9 PYTHON_ARCH: "32"
9 PYTHON_ARCH: "32"
10
10
11 - PYTHON: "C:\\Python34-x64"
12 PYTHON_VERSION: "3.4.x"
13 PYTHON_ARCH: "64"
14
15 - PYTHON: "C:\\Python36-x64"
11 - PYTHON: "C:\\Python36-x64"
16 PYTHON_VERSION: "3.6.x"
12 PYTHON_VERSION: "3.6.x"
17 PYTHON_ARCH: "64"
13 PYTHON_ARCH: "64"
@@ -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,31 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
38
14
39
15 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
40 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
16
41
@@ -1,3 +1,6 b''
1 Deprecations
2 ============
3
1 A couple of unused function and methods have been deprecated and will be removed
4 A couple of unused function and methods have been deprecated and will be removed
2 in future versions:
5 in future versions:
3
6
@@ -26,7 +26,7 b' import sys'
26 #
26 #
27 # This check is also made in IPython/__init__, don't forget to update both when
27 # This check is also made in IPython/__init__, don't forget to update both when
28 # changing Python version requirements.
28 # changing Python version requirements.
29 if sys.version_info < (3, 4):
29 if sys.version_info < (3, 5):
30 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
30 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
31 try:
31 try:
32 import pip
32 import pip
@@ -42,9 +42,9 b' if sys.version_info < (3, 4):'
42
42
43
43
44 error = """
44 error = """
45 IPython 7.0+ supports Python 3.4 and above.
45 IPython 7.0+ supports Python 3.5 and above.
46 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
46 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
47 Python 3.3 was supported up to IPython 6.x.
47 Python 3.3 and 3.4 were supported up to IPython 6.x.
48
48
49 See IPython `README.rst` file for more information:
49 See IPython `README.rst` file for more information:
50
50
@@ -230,7 +230,7 b' for key, deps in extras_require.items():'
230 extras_require['all'] = everything
230 extras_require['all'] = everything
231
231
232 if 'setuptools' in sys.modules:
232 if 'setuptools' in sys.modules:
233 setuptools_extra_args['python_requires'] = '>=3.4'
233 setuptools_extra_args['python_requires'] = '>=3.5'
234 setuptools_extra_args['zip_safe'] = False
234 setuptools_extra_args['zip_safe'] = False
235 setuptools_extra_args['entry_points'] = {
235 setuptools_extra_args['entry_points'] = {
236 'console_scripts': find_entry_points(),
236 'console_scripts': find_entry_points(),
General Comments 0
You need to be logged in to leave comments. Login now