Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[experiment] Async shell (run everything under asyncio) #11347

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 8 additions & 8 deletions IPython/core/interactiveshell.py
Expand Up @@ -2812,16 +2812,20 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr
result : :class:`ExecutionResult`
"""
result = None
if self.should_run_async(raw_cell):
runner = self.loop_runner
else:
runner = _pseudo_sync_runner
try:
result = self._run_cell(
raw_cell, store_history, silent, shell_futures)
result = runner(self._run_cell(
raw_cell, store_history, silent, shell_futures))
finally:
self.events.trigger('post_execute')
if not silent:
self.events.trigger('post_run_cell', result)
return result

def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures:bool):
async def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures:bool):
"""Internal method to run a complete IPython cell."""
coro = self.run_cell_async(
raw_cell,
Expand All @@ -2834,13 +2838,9 @@ def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures
# 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:
return runner(coro)
return await coro
except BaseException as e:
info = ExecutionInfo(raw_cell, store_history, silent, shell_futures)
result = ExecutionResult(info)
Expand Down
43 changes: 38 additions & 5 deletions IPython/terminal/interactiveshell.py
Expand Up @@ -3,9 +3,13 @@
import os
import sys
import warnings

import asyncio

from warnings import warn

from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
from IPython.core.async_helpers import _pseudo_sync_runner
from IPython.utils import io
from IPython.utils.py3compat import input
from IPython.utils.terminal import toggle_set_term_title, set_term_title
Expand All @@ -25,6 +29,7 @@
from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
from prompt_toolkit.styles import DynamicStyle, merge_styles
from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
from prompt_toolkit.eventloop.defaults import use_asyncio_event_loop

from pygments.styles import get_style_by_name
from pygments.style import Style
Expand All @@ -44,6 +49,12 @@ class _NoStyle(Style): pass




def mark_original(function):
function.original = True
return function


_style_overrides_light_bg = {
Token.Prompt: '#0000ff',
Token.PromptNum: '#0000ee bold',
Expand Down Expand Up @@ -234,9 +245,11 @@ def prompt():
while self.check_complete('\n'.join(lines))[0] == 'incomplete':
lines.append( input(prompt_continuation) )
return '\n'.join(lines)
# asyncify this.
self.prompt_for_code = prompt
return

use_asyncio_event_loop()
# Set up keyboard shortcuts
key_bindings = create_ipython_shortcuts(self)

Expand All @@ -256,6 +269,8 @@ def prompt():

editing_mode = getattr(EditingMode, self.editing_mode.upper())


# Tell prompt_toolkit to use the asyncio event loop.
self.pt_app = PromptSession(
editing_mode=editing_mode,
key_bindings=key_bindings,
Expand Down Expand Up @@ -370,17 +385,23 @@ def get_message():
'inputhook': self.inputhook,
}

@mark_original
def prompt_for_code(self):
return _pseudo_sync_runner(self.prompt_for_code_async())


async def prompt_for_code_async(self):
if self.rl_next_input:
default = self.rl_next_input
self.rl_next_input = None
else:
default = ''

with patch_stdout(raw=True):
text = self.pt_app.prompt(
text = await self.pt_app.prompt(
default=default,
# pre_run=self.pre_prompt,# reset_current_buffer=True,
async_=True,
**self._extra_prompt_options())
return text

Expand Down Expand Up @@ -442,7 +463,10 @@ def ask_exit(self):

rl_next_input = None

def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
def interact(self):
return _pseudo_sync_runner(self.interact_async())

async def interact_async(self, display_banner=DISPLAY_BANNER_DEPRECATED):

if display_banner is not DISPLAY_BANNER_DEPRECATED:
warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
Expand All @@ -452,26 +476,35 @@ def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
print(self.separate_in, end='')

try:
code = self.prompt_for_code()
if getattr(self.prompt_for_code, 'original', False):
code = await self.prompt_for_code_async()
else:
code = self.prompt_for_code()
except EOFError:
if (not self.confirm_exit) \
or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
self.ask_exit()

else:
if code:
self.run_cell(code, store_history=True)
await self.run_cell_async(code, store_history=True)

def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
# An extra layer of protection in case someone mashing Ctrl-C breaks
# out of our internal code.
if display_banner is not DISPLAY_BANNER_DEPRECATED:
warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.ensure_future(self._mainloop()))

async def _mainloop(self):
while True:
try:
self.interact()
await self.interact_async()
break
except KeyboardInterrupt as e:
import traceback
traceback.print_exc()
print("\n%s escaped interact()\n" % type(e).__name__)
finally:
# An interrupt during the eventloop will mess up the
Expand Down