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, |
|
30 | if sys.version_info < (3, 5): | |
31 | raise ImportError( |
|
31 | raise ImportError( | |
32 | """ |
|
32 | """ | |
33 |
IPython 7.0+ supports Python 3. |
|
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 |
|
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 = |
|
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_equal |
|
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 |
|
|
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()") |
|
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()") |
|
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, |
|
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. |
|
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 |
|
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. |
|
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