Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 63 additions & 124 deletions Lib/asyncio/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@
import ast
import asyncio
import asyncio.tools
import concurrent.futures
import contextvars
import inspect
import os
import site
import sys
import threading
import types
import warnings

from _colorize import get_theme
from _pyrepl.console import InteractiveColoredConsole

from . import futures


class AsyncIOInteractiveConsole(InteractiveColoredConsole):

Expand All @@ -29,122 +25,83 @@ def __init__(self, locals, loop):

def runcode(self, code):
global return_code
future = concurrent.futures.Future()

def callback():
global return_code
global repl_future
global keyboard_interrupted

repl_future = None
keyboard_interrupted = False

async def callback():
func = types.FunctionType(code, self.locals)
try:
coro = func()
except SystemExit as se:
return_code = se.code
self.loop.stop()
return
except KeyboardInterrupt as ex:
keyboard_interrupted = True
future.set_exception(ex)
return
except BaseException as ex:
future.set_exception(ex)
return
coro = func()

if not inspect.iscoroutine(coro):
future.set_result(coro)
return

try:
repl_future = self.loop.create_task(coro, context=self.context)
futures._chain_future(repl_future, future)
except BaseException as exc:
future.set_exception(exc)
return coro
return await coro

self.loop.call_soon_threadsafe(callback, context=self.context)

task = self.loop.create_task(callback(), context=self.context)
try:
return future.result()
except SystemExit as se:
return_code = se.code
self.loop.stop()
return
return self.loop.run_until_complete(task)
except SystemExit:
raise
except BaseException:
if keyboard_interrupted:
if not CAN_USE_PYREPL:
self.write("\nKeyboardInterrupt\n")
else:
self.showtraceback()
self.showtraceback()
return self.STATEMENT_FAILED

class REPLThread(threading.Thread):

def run(self):
global return_code
def interact():
global return_code

try:
banner = (
f'asyncio REPL {sys.version} on {sys.platform}\n'
f'Use "await" directly instead of "asyncio.run()".\n'
f'Type "help", "copyright", "credits" or "license" '
f'for more information.\n'
)
try:
banner = (
f'asyncio REPL {sys.version} on {sys.platform}\n'
f'Use "await" directly instead of "asyncio.run()".\n'
f'Type "help", "copyright", "credits" or "license" '
f'for more information.\n'
)

console.write(banner)

if startup_path := os.getenv("PYTHONSTARTUP"):
sys.audit("cpython.run_startup", startup_path)

import tokenize
with tokenize.open(startup_path) as f:
startup_code = compile(f.read(), startup_path, "exec")
exec(startup_code, console.locals)

ps1 = getattr(sys, "ps1", ">>> ")
if CAN_USE_PYREPL:
theme = get_theme().syntax
ps1 = f"{theme.prompt}{ps1}{theme.reset}"
import_line = f'{theme.keyword}import{theme.reset} asyncio'
else:
import_line = "import asyncio"
console.write(f"{ps1}{import_line}\n")

console.write(banner)

if startup_path := os.getenv("PYTHONSTARTUP"):
sys.audit("cpython.run_startup", startup_path)

import tokenize
with tokenize.open(startup_path) as f:
startup_code = compile(f.read(), startup_path, "exec")
exec(startup_code, console.locals)

ps1 = getattr(sys, "ps1", ">>> ")
if CAN_USE_PYREPL:
theme = get_theme().syntax
ps1 = f"{theme.prompt}{ps1}{theme.reset}"
import_line = f'{theme.keyword}import{theme.reset} asyncio'
else:
import_line = "import asyncio"
console.write(f"{ps1}{import_line}\n")

if CAN_USE_PYREPL:
from _pyrepl.simple_interact import (
run_multiline_interactive_console,
)
try:
sys.ps1 = ps1
run_multiline_interactive_console(console)
except SystemExit:
# expected via the `exit` and `quit` commands
pass
except BaseException:
# unexpected issue
console.showtraceback()
console.write("Internal error, ")
return_code = 1
else:
if CAN_USE_PYREPL:
from _pyrepl.simple_interact import (
run_multiline_interactive_console,
)
try:
sys.ps1 = ps1
run_multiline_interactive_console(console)
except SystemExit as se:
# expected via the `exit` and `quit` commands
return_code = se.code
except BaseException:
# unexpected issue
console.showtraceback()
console.write("Internal error, ")
return_code = 1
else:
try:
console.interact(banner="", exitmsg="")
finally:
warnings.filterwarnings(
'ignore',
message=r'^coroutine .* was never awaited$',
category=RuntimeWarning)

loop.call_soon_threadsafe(loop.stop)

def interrupt(self) -> None:
if not CAN_USE_PYREPL:
return
except SystemExit as se:
return_code = se.code
finally:
warnings.filterwarnings(
'ignore',
message=r'^coroutine .* was never awaited$',
category=RuntimeWarning)

from _pyrepl.simple_interact import _get_reader
r = _get_reader()
if r.threading_hook is not None:
r.threading_hook.add("") # type: ignore
loop.call_soon_threadsafe(loop.stop)


if __name__ == '__main__':
Expand Down Expand Up @@ -198,9 +155,6 @@ def interrupt(self) -> None:

console = AsyncIOInteractiveConsole(repl_locals, loop)

repl_future = None
keyboard_interrupted = False

try:
import readline # NoQA
except ImportError:
Expand All @@ -223,21 +177,6 @@ def interrupt(self) -> None:
completer = rlcompleter.Completer(console.locals)
readline.set_completer(completer.complete)

repl_thread = REPLThread(name="Interactive thread")
repl_thread.daemon = True
repl_thread.start()

while True:
try:
loop.run_forever()
except KeyboardInterrupt:
keyboard_interrupted = True
if repl_future and not repl_future.done():
repl_future.cancel()
repl_thread.interrupt()
continue
else:
break

interact()
console.write('exiting asyncio REPL...\n')
sys.exit(return_code)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Refactor and simplify the asyncio REPL by removing the threading and future handling.
This fixes an issue on MacOS that the readline module will be broken after pressing Ctrl+C.
Loading