Show More
@@ -83,7 +83,7 b' from logging import error' | |||||
83 |
import IPython.core.hooks |
|
83 | import IPython.core.hooks | |
84 |
|
84 | |||
85 |
from typing import List as ListType, Tuple, Optional, Callable |
|
85 | from typing import List as ListType, Tuple, Optional, Callable | |
86 |
from ast import |
|
86 | from ast import stmt | |
87 |
|
87 | |||
88 |
|
88 | |||
89 |
# NoOpContext is deprecated, but ipykernel imports it from here. |
|
89 | # NoOpContext is deprecated, but ipykernel imports it from here. | |
@@ -112,13 +112,7 b' class ProvisionalWarning(DeprecationWarning):' | |||||
112 |
""" |
|
112 | """ | |
113 |
pass |
|
113 | pass | |
114 |
|
114 | |||
115 | if sys.version_info > (3,8): |
|
115 | from ast import Module | |
116 | from ast import Module |
|
|||
117 | else : |
|
|||
118 | # mock the new API, ignore second argument |
|
|||
119 | # see https://github.com/ipython/ipython/issues/11590 |
|
|||
120 | from ast import Module as OriginalModule |
|
|||
121 | Module = lambda nodelist, type_ignores: OriginalModule(nodelist) |
|
|||
122 |
|
116 | |||
123 |
_assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign) |
|
117 | _assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign) | |
124 |
_single_targets_nodes = (ast.AugAssign, ast.AnnAssign) |
|
118 | _single_targets_nodes = (ast.AugAssign, ast.AnnAssign) | |
@@ -127,104 +121,11 b' _single_targets_nodes = (ast.AugAssign, ast.AnnAssign)' | |||||
127 |
# Await Helpers |
|
121 | # Await Helpers | |
128 |
#----------------------------------------------------------------------------- |
|
122 | #----------------------------------------------------------------------------- | |
129 |
|
123 | |||
130 | def removed_co_newlocals(function:types.FunctionType) -> types.FunctionType: |
|
|||
131 | """Return a function that do not create a new local scope. |
|
|||
132 |
|
||||
133 | Given a function, create a clone of this function where the co_newlocal flag |
|
|||
134 | has been removed, making this function code actually run in the sourounding |
|
|||
135 | scope. |
|
|||
136 |
|
||||
137 | We need this in order to run asynchronous code in user level namespace. |
|
|||
138 | """ |
|
|||
139 | from types import CodeType, FunctionType |
|
|||
140 | CO_NEWLOCALS = 0x0002 |
|
|||
141 | code = function.__code__ |
|
|||
142 | new_co_flags = code.co_flags & ~CO_NEWLOCALS |
|
|||
143 | if sys.version_info > (3, 8, 0, 'alpha', 3): |
|
|||
144 | new_code = code.replace(co_flags=new_co_flags) |
|
|||
145 | else: |
|
|||
146 | new_code = CodeType( |
|
|||
147 | code.co_argcount, |
|
|||
148 | code.co_kwonlyargcount, |
|
|||
149 | code.co_nlocals, |
|
|||
150 | code.co_stacksize, |
|
|||
151 | new_co_flags, |
|
|||
152 | code.co_code, |
|
|||
153 | code.co_consts, |
|
|||
154 | code.co_names, |
|
|||
155 | code.co_varnames, |
|
|||
156 | code.co_filename, |
|
|||
157 | code.co_name, |
|
|||
158 | code.co_firstlineno, |
|
|||
159 | code.co_lnotab, |
|
|||
160 | code.co_freevars, |
|
|||
161 | code.co_cellvars |
|
|||
162 | ) |
|
|||
163 | return FunctionType(new_code, globals(), function.__name__, function.__defaults__) |
|
|||
164 |
|
||||
165 |
|
||||
166 |
# we still need to run things using the asyncio eventloop, but there is no |
|
124 | # we still need to run things using the asyncio eventloop, but there is no | |
167 |
# async integration |
|
125 | # async integration | |
168 |
from .async_helpers import |
|
126 | from .async_helpers import _asyncio_runner, _pseudo_sync_runner | |
169 |
from .async_helpers import _curio_runner, _trio_runner, _should_be_async |
|
127 | from .async_helpers import _curio_runner, _trio_runner, _should_be_async | |
170 |
|
128 | |||
171 |
|
||||
172 | def _ast_asyncify(cell:str, wrapper_name:str) -> ast.Module: |
|
|||
173 | """ |
|
|||
174 | Parse a cell with top-level await and modify the AST to be able to run it later. |
|
|||
175 |
|
||||
176 | Parameters |
|
|||
177 | ---------- |
|
|||
178 | cell: str |
|
|||
179 | The code cell to asyncronify |
|
|||
180 | wrapper_name: str |
|
|||
181 | The name of the function to be used to wrap the passed `cell`. It is |
|
|||
182 | advised to **not** use a python identifier in order to not pollute the |
|
|||
183 | global namespace in which the function will be ran. |
|
|||
184 |
|
||||
185 | Returns |
|
|||
186 | ------- |
|
|||
187 | ModuleType: |
|
|||
188 | A module object AST containing **one** function named `wrapper_name`. |
|
|||
189 |
|
||||
190 | The given code is wrapped in a async-def function, parsed into an AST, and |
|
|||
191 | the resulting function definition AST is modified to return the last |
|
|||
192 | expression. |
|
|||
193 |
|
||||
194 | The last expression or await node is moved into a return statement at the |
|
|||
195 | end of the function, and removed from its original location. If the last |
|
|||
196 | node is not Expr or Await nothing is done. |
|
|||
197 |
|
||||
198 | The function `__code__` will need to be later modified (by |
|
|||
199 | ``removed_co_newlocals``) in a subsequent step to not create new `locals()` |
|
|||
200 | meaning that the local and global scope are the same, ie as if the body of |
|
|||
201 | the function was at module level. |
|
|||
202 |
|
||||
203 | Lastly a call to `locals()` is made just before the last expression of the |
|
|||
204 | function, or just after the last assignment or statement to make sure the |
|
|||
205 | global dict is updated as python function work with a local fast cache which |
|
|||
206 | is updated only on `local()` calls. |
|
|||
207 | """ |
|
|||
208 |
|
||||
209 | from ast import Expr, Await, Return, stmt, FunctionDef, Try, AsyncFunctionDef |
|
|||
210 | if sys.version_info >= (3,8): |
|
|||
211 | return ast.parse(cell) |
|
|||
212 | tree = ast.parse(_asyncify(cell)) |
|
|||
213 |
|
||||
214 | function_def = tree.body[0] |
|
|||
215 | if sys.version_info > (3, 8): |
|
|||
216 | assert isinstance(function_def, FunctionDef), function_def |
|
|||
217 | else: |
|
|||
218 | assert isinstance(function_def, (FunctionDef, AsyncFunctionDef)), function_def |
|
|||
219 |
|
||||
220 | function_def.name = wrapper_name |
|
|||
221 | try_block = function_def.body[0] |
|
|||
222 | assert isinstance(try_block, Try) |
|
|||
223 | lastexpr = try_block.body[-1] |
|
|||
224 | if isinstance(lastexpr, (Expr, Await)): |
|
|||
225 | try_block.body[-1] = Return(lastexpr.value) |
|
|||
226 | ast.fix_missing_locations(tree) |
|
|||
227 | return tree |
|
|||
228 |
#----------------------------------------------------------------------------- |
|
129 | #----------------------------------------------------------------------------- | |
229 |
# Globals |
|
130 | # Globals | |
230 |
#----------------------------------------------------------------------------- |
|
131 | #----------------------------------------------------------------------------- | |
@@ -696,7 +597,6 b' class InteractiveShell(SingletonConfigurable):' | |||||
696 |
self.init_pdb() |
|
597 | self.init_pdb() | |
697 |
self.init_extension_manager() |
|
598 | self.init_extension_manager() | |
698 |
self.init_payload() |
|
599 | self.init_payload() | |
699 | self.init_deprecation_warnings() |
|
|||
700 |
self.hooks.late_startup_hook() |
|
600 | self.hooks.late_startup_hook() | |
701 |
self.events.trigger('shell_initialized', self) |
|
601 | self.events.trigger('shell_initialized', self) | |
702 |
atexit.register(self.atexit_operations) |
|
602 | atexit.register(self.atexit_operations) | |
@@ -822,16 +722,6 b' class InteractiveShell(SingletonConfigurable):' | |||||
822 |
elif self.logstart: |
|
722 | elif self.logstart: | |
823 |
self.magic('logstart') |
|
723 | self.magic('logstart') | |
824 |
|
724 | |||
825 | def init_deprecation_warnings(self): |
|
|||
826 | """ |
|
|||
827 | register default filter for deprecation warning. |
|
|||
828 |
|
||||
829 | This will allow deprecation warning of function used interactively to show |
|
|||
830 | warning to users, and still hide deprecation warning from libraries import. |
|
|||
831 | """ |
|
|||
832 | if sys.version_info < (3,7): |
|
|||
833 | warnings.filterwarnings("default", category=DeprecationWarning, module=self.user_ns.get("__name__")) |
|
|||
834 |
|
||||
835 |
|
725 | |||
836 |
def init_builtins(self): |
|
726 | def init_builtins(self): | |
837 |
# A single, static flag that we set to True. Its presence indicates |
|
727 | # A single, static flag that we set to True. Its presence indicates | |
@@ -3153,28 +3043,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
3153 |
with self.display_trap: |
|
3043 | with self.display_trap: | |
3154 |
# Compile to bytecode |
|
3044 | # Compile to bytecode | |
3155 |
try: |
|
3045 | try: | |
3156 | if sys.version_info < (3,8) and self.autoawait: |
|
3046 | code_ast = compiler.ast_parse(cell, filename=cell_name) | |
3157 | if _should_be_async(cell): |
|
|||
3158 | # the code AST below will not be user code: we wrap it |
|
|||
3159 | # in an `async def`. This will likely make some AST |
|
|||
3160 | # transformer below miss some transform opportunity and |
|
|||
3161 | # introduce a small coupling to run_code (in which we |
|
|||
3162 | # bake some assumptions of what _ast_asyncify returns. |
|
|||
3163 | # they are ways around (like grafting part of the ast |
|
|||
3164 | # later: |
|
|||
3165 | # - Here, return code_ast.body[0].body[1:-1], as well |
|
|||
3166 | # as last expression in return statement which is |
|
|||
3167 | # the user code part. |
|
|||
3168 | # - Let it go through the AST transformers, and graft |
|
|||
3169 | # - it back after the AST transform |
|
|||
3170 | # But that seem unreasonable, at least while we |
|
|||
3171 | # do not need it. |
|
|||
3172 | code_ast = _ast_asyncify(cell, 'async-def-wrapper') |
|
|||
3173 | _run_async = True |
|
|||
3174 | else: |
|
|||
3175 | code_ast = compiler.ast_parse(cell, filename=cell_name) |
|
|||
3176 | else: |
|
|||
3177 | code_ast = compiler.ast_parse(cell, filename=cell_name) |
|
|||
3178 |
except self.custom_exceptions as e: |
|
3047 | except self.custom_exceptions as e: | |
3179 |
etype, value, tb = sys.exc_info() |
|
3048 | etype, value, tb = sys.exc_info() | |
3180 |
self.CustomTB(etype, value, tb) |
|
3049 | self.CustomTB(etype, value, tb) | |
@@ -3200,8 +3069,6 b' class InteractiveShell(SingletonConfigurable):' | |||||
3200 |
|
3069 | |||
3201 |
# Execute the user code |
|
3070 | # Execute the user code | |
3202 |
interactivity = "none" if silent else self.ast_node_interactivity |
|
3071 | interactivity = "none" if silent else self.ast_node_interactivity | |
3203 | if _run_async: |
|
|||
3204 | interactivity = 'async' |
|
|||
3205 |
|
3072 | |||
3206 |
has_raised = await self.run_ast_nodes(code_ast.body, cell_name, |
|
3073 | has_raised = await self.run_ast_nodes(code_ast.body, cell_name, | |
3207 |
interactivity=interactivity, compiler=compiler, result=result) |
|
3074 | interactivity=interactivity, compiler=compiler, result=result) | |
@@ -3310,11 +3177,6 b' class InteractiveShell(SingletonConfigurable):' | |||||
3310 |
or the last assignment. Other values for this parameter will raise a |
|
3177 | or the last assignment. Other values for this parameter will raise a | |
3311 |
ValueError. |
|
3178 | ValueError. | |
3312 |
|
3179 | |||
3313 | Experimental value: 'async' Will try to run top level interactive |
|
|||
3314 | async/await code in default runner, this will not respect the |
|
|||
3315 | interactivity setting and will only run the last node if it is an |
|
|||
3316 | expression. |
|
|||
3317 |
|
||||
3318 |
compiler : callable |
|
3180 | compiler : callable | |
3319 |
A function with the same interface as the built-in compile(), to turn |
|
3181 | A function with the same interface as the built-in compile(), to turn | |
3320 |
the AST nodes into code objects. Default is the built-in compile(). |
|
3182 | the AST nodes into code objects. Default is the built-in compile(). | |
@@ -3358,52 +3220,37 b' class InteractiveShell(SingletonConfigurable):' | |||||
3358 |
to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:] |
|
3220 | to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:] | |
3359 |
elif interactivity == 'all': |
|
3221 | elif interactivity == 'all': | |
3360 |
to_run_exec, to_run_interactive = [], nodelist |
|
3222 | to_run_exec, to_run_interactive = [], nodelist | |
3361 | elif interactivity == 'async': |
|
|||
3362 | to_run_exec, to_run_interactive = [], nodelist |
|
|||
3363 | _async = True |
|
|||
3364 |
else: |
|
3223 | else: | |
3365 |
raise ValueError("Interactivity was %r" % interactivity) |
|
3224 | raise ValueError("Interactivity was %r" % interactivity) | |
3366 |
|
3225 | |||
3367 |
try: |
|
3226 | try: | |
3368 | if _async and sys.version_info > (3,8): |
|
3227 | ||
3369 | raise ValueError("This branch should never happen on Python 3.8 and above, " |
|
3228 | def compare(code): | |
3370 | "please try to upgrade IPython and open a bug report with your case.") |
|
3229 | is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE | |
3371 |
|
|
3230 | return is_async | |
3372 | # If interactivity is async the semantics of run_code are |
|
3231 | ||
3373 | # completely different Skip usual machinery. |
|
3232 | # refactor that to just change the mod constructor. | |
3374 | mod = Module(nodelist, []) |
|
3233 | to_run = [] | |
3375 | async_wrapper_code = compiler(mod, cell_name, 'exec') |
|
3234 | for node in to_run_exec: | |
3376 | exec(async_wrapper_code, self.user_global_ns, self.user_ns) |
|
3235 | to_run.append((node, "exec")) | |
3377 | async_code = removed_co_newlocals(self.user_ns.pop('async-def-wrapper')).__code__ |
|
3236 | ||
3378 | if (await self.run_code(async_code, result, async_=True)): |
|
3237 | for node in to_run_interactive: | |
|
3238 | to_run.append((node, "single")) | |||
|
3239 | ||||
|
3240 | for node, mode in to_run: | |||
|
3241 | if mode == "exec": | |||
|
3242 | mod = Module([node], []) | |||
|
3243 | elif mode == "single": | |||
|
3244 | mod = ast.Interactive([node]) | |||
|
3245 | with compiler.extra_flags( | |||
|
3246 | getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) | |||
|
3247 | if self.autoawait | |||
|
3248 | else 0x0 | |||
|
3249 | ): | |||
|
3250 | code = compiler(mod, cell_name, mode) | |||
|
3251 | asy = compare(code) | |||
|
3252 | if await self.run_code(code, result, async_=asy): | |||
3379 |
return True |
|
3253 | return True | |
3380 | else: |
|
|||
3381 | if sys.version_info > (3, 8): |
|
|||
3382 | def compare(code): |
|
|||
3383 | is_async = (inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE) |
|
|||
3384 | return is_async |
|
|||
3385 | else: |
|
|||
3386 | def compare(code): |
|
|||
3387 | return _async |
|
|||
3388 |
|
||||
3389 | # refactor that to just change the mod constructor. |
|
|||
3390 | to_run = [] |
|
|||
3391 | for node in to_run_exec: |
|
|||
3392 | to_run.append((node, 'exec')) |
|
|||
3393 |
|
||||
3394 | for node in to_run_interactive: |
|
|||
3395 | to_run.append((node, 'single')) |
|
|||
3396 |
|
||||
3397 | for node,mode in to_run: |
|
|||
3398 | if mode == 'exec': |
|
|||
3399 | mod = Module([node], []) |
|
|||
3400 | elif mode == 'single': |
|
|||
3401 | mod = ast.Interactive([node]) |
|
|||
3402 | with compiler.extra_flags(getattr(ast, 'PyCF_ALLOW_TOP_LEVEL_AWAIT', 0x0) if self.autoawait else 0x0): |
|
|||
3403 | code = compiler(mod, cell_name, mode) |
|
|||
3404 | asy = compare(code) |
|
|||
3405 | if (await self.run_code(code, result, async_=asy)): |
|
|||
3406 | return True |
|
|||
3407 |
|
3254 | |||
3408 |
# Flush softspace |
|
3255 | # Flush softspace | |
3409 |
if softspace(sys.stdout, 0): |
|
3256 | if softspace(sys.stdout, 0): | |
@@ -3426,21 +3273,6 b' class InteractiveShell(SingletonConfigurable):' | |||||
3426 |
|
3273 | |||
3427 |
return False |
|
3274 | return False | |
3428 |
|
3275 | |||
3429 | def _async_exec(self, code_obj: types.CodeType, user_ns: dict): |
|
|||
3430 | """ |
|
|||
3431 | Evaluate an asynchronous code object using a code runner |
|
|||
3432 |
|
||||
3433 | Fake asynchronous execution of code_object in a namespace via a proxy namespace. |
|
|||
3434 |
|
||||
3435 | Returns coroutine object, which can be executed via async loop runner |
|
|||
3436 |
|
||||
3437 | WARNING: The semantics of `async_exec` are quite different from `exec`, |
|
|||
3438 | in particular you can only pass a single namespace. It also return a |
|
|||
3439 | handle to the value of the last things returned by code_object. |
|
|||
3440 | """ |
|
|||
3441 |
|
||||
3442 | return eval(code_obj, user_ns) |
|
|||
3443 |
|
||||
3444 |
async def run_code(self, code_obj, result=None, *, async_=False): |
|
3276 | async def run_code(self, code_obj, result=None, *, async_=False): | |
3445 |
"""Execute a code object. |
|
3277 | """Execute a code object. | |
3446 |
|
3278 | |||
@@ -3475,11 +3307,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
3475 |
try: |
|
3307 | try: | |
3476 |
try: |
|
3308 | try: | |
3477 |
self.hooks.pre_run_code_hook() |
|
3309 | self.hooks.pre_run_code_hook() | |
3478 |
if async_ |
|
3310 | if async_: | |
3479 | last_expr = (await self._async_exec(code_obj, self.user_ns)) |
|
|||
3480 | code = compile('last_expr', 'fake', "single") |
|
|||
3481 | exec(code, {'last_expr': last_expr}) |
|
|||
3482 | elif async_ : |
|
|||
3483 |
await eval(code_obj, self.user_global_ns, self.user_ns) |
|
3311 | await eval(code_obj, self.user_global_ns, self.user_ns) | |
3484 |
else: |
|
3312 | else: | |
3485 |
exec(code_obj, self.user_global_ns, self.user_ns) |
|
3313 | exec(code_obj, self.user_global_ns, self.user_ns) |
General Comments 0
You need to be logged in to leave comments.
Login now