Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 45 additions & 25 deletions IPython/core/interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -2830,32 +2830,52 @@ def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures
shell_futures=shell_futures,
)

# run_cell_async is async, but may not actually need and eventloop.
# run_cell_async is async, but may not actually need an eventloop.
# when this is the case, we want to run it using the pseudo_sync_runner
# so that code can invoke eventloops (for example via the %run , and
# `%paste` magic.
if self.should_run_async(raw_cell):
runner = self.loop_runner
else:
runner = _pseudo_sync_runner

try:
interactivity = coro.send(None)
except StopIteration as exc:
return exc.value
return runner(coro)
except Exception as e:
info = ExecutionInfo(raw_cell, store_history, silent, shell_futures)
result = ExecutionResult(info)
result.error_in_exec = e
self.showtraceback(running_compiled_code=True)
return result
return

# if code was not async, sending `None` was actually executing the code.
if isinstance(interactivity, ExecutionResult):
return interactivity
def should_run_async(self, raw_cell: str) -> bool:
"""Return whether a cell should be run asynchronously via a coroutine runner

if interactivity == 'async':
try:
return self.loop_runner(coro)
except Exception as e:
info = ExecutionInfo(raw_cell, store_history, silent, shell_futures)
result = ExecutionResult(info)
result.error_in_exec = e
self.showtraceback(running_compiled_code=True)
return result
return _pseudo_sync_runner(coro)
Parameters
----------
raw_cell: str
The code to be executed

Returns
-------
result: bool
Whether the code needs to be run with a coroutine runner or not

.. versionadded: 7.0
"""
if not self.autoawait:
return False
try:
cell = self.transform_cell(raw_cell)
except Exception:
# any exception during transform will be raised
# prior to execution
return False
return _should_be_async(cell)

@asyncio.coroutine
def run_cell_async(self, raw_cell:str, store_history=False, silent=False, shell_futures=True) -> ExecutionResult:
def run_cell_async(self, raw_cell: str, store_history=False, silent=False, shell_futures=True) -> ExecutionResult:
"""Run a complete IPython cell asynchronously.

Parameters
Expand All @@ -2878,6 +2898,8 @@ def run_cell_async(self, raw_cell:str, store_history=False, silent=False, shell_
Returns
-------
result : :class:`ExecutionResult`

.. versionadded: 7.0
"""
info = ExecutionInfo(
raw_cell, store_history, silent, shell_futures)
Expand Down Expand Up @@ -2910,13 +2932,13 @@ def error_before_exec(value):
# prefilter_manager) raises an exception, we store it in this variable
# so that we can display the error after logging the input and storing
# it in the history.
preprocessing_exc_tuple = None
try:
# Static input transformations
cell = self.transform_cell(raw_cell)
except Exception:
preprocessing_exc_tuple = sys.exc_info()
cell = raw_cell # cell has to exist so it can be stored/logged
else:
preprocessing_exc_tuple = None

# Store raw and processed history
if store_history:
Expand Down Expand Up @@ -2991,12 +3013,10 @@ def error_before_exec(value):
interactivity = "none" if silent else self.ast_node_interactivity
if _run_async:
interactivity = 'async'
# yield interactivity so let run_cell decide whether to use
# an async loop_runner
yield interactivity

has_raised = yield from self.run_ast_nodes(code_ast.body, cell_name,
interactivity=interactivity, compiler=compiler, result=result)

self.last_execution_succeeded = not has_raised
self.last_execution_result = result

Expand Down Expand Up @@ -3042,7 +3062,7 @@ def transform_cell(self, raw_cell):
cell = ''.join(lines)

return cell

def transform_ast(self, node):
"""Apply the AST transformations from self.ast_transformers

Expand Down
18 changes: 18 additions & 0 deletions IPython/core/tests/test_interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

import asyncio
import ast
import os
import signal
Expand All @@ -24,6 +25,7 @@

from IPython.core.error import InputRejected
from IPython.core.inputtransformer import InputTransformer
from IPython.core import interactiveshell
from IPython.testing.decorators import (
skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
)
Expand Down Expand Up @@ -928,3 +930,19 @@ def test_custom_exc_count():
ip.set_custom_exc((), None)
nt.assert_equal(hook.call_count, 1)
nt.assert_equal(ip.execution_count, before + 1)


def test_run_cell_async():
loop = asyncio.get_event_loop()
ip.run_cell("import asyncio")
coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
assert asyncio.iscoroutine(coro)
result = loop.run_until_complete(coro)
assert isinstance(result, interactiveshell.ExecutionResult)
assert result.result == 5


def test_should_run_async():
assert not ip.should_run_async("a = 5")
assert ip.should_run_async("await x")
assert ip.should_run_async("import asyncio; await asyncio.sleep(1)")