This commit is contained in:
Frost Ming 2025-12-08 12:15:30 +08:00 committed by GitHub
commit 0f1460b374
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 65 additions and 124 deletions

View file

@ -2,21 +2,17 @@
import ast import ast
import asyncio import asyncio
import asyncio.tools import asyncio.tools
import concurrent.futures
import contextvars import contextvars
import inspect import inspect
import os import os
import site import site
import sys import sys
import threading
import types import types
import warnings import warnings
from _colorize import get_theme from _colorize import get_theme
from _pyrepl.console import InteractiveColoredConsole from _pyrepl.console import InteractiveColoredConsole
from . import futures
class AsyncIOInteractiveConsole(InteractiveColoredConsole): class AsyncIOInteractiveConsole(InteractiveColoredConsole):
@ -29,122 +25,83 @@ def __init__(self, locals, loop):
def runcode(self, code): def runcode(self, code):
global return_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) func = types.FunctionType(code, self.locals)
try: coro = func()
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
if not inspect.iscoroutine(coro): if not inspect.iscoroutine(coro):
future.set_result(coro) return coro
return return await coro
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)
self.loop.call_soon_threadsafe(callback, context=self.context)
task = self.loop.create_task(callback(), context=self.context)
try: try:
return future.result() return self.loop.run_until_complete(task)
except SystemExit as se: except SystemExit:
return_code = se.code raise
self.loop.stop()
return
except BaseException: except BaseException:
if keyboard_interrupted: self.showtraceback()
if not CAN_USE_PYREPL:
self.write("\nKeyboardInterrupt\n")
else:
self.showtraceback()
return self.STATEMENT_FAILED return self.STATEMENT_FAILED
class REPLThread(threading.Thread):
def run(self): def interact():
global return_code global return_code
try: try:
banner = ( banner = (
f'asyncio REPL {sys.version} on {sys.platform}\n' f'asyncio REPL {sys.version} on {sys.platform}\n'
f'Use "await" directly instead of "asyncio.run()".\n' f'Use "await" directly instead of "asyncio.run()".\n'
f'Type "help", "copyright", "credits" or "license" ' f'Type "help", "copyright", "credits" or "license" '
f'for more information.\n' 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")
if CAN_USE_PYREPL:
from _pyrepl.simple_interact import (
run_multiline_interactive_console,
) )
try:
console.write(banner) sys.ps1 = ps1
run_multiline_interactive_console(console)
if startup_path := os.getenv("PYTHONSTARTUP"): except SystemExit as se:
sys.audit("cpython.run_startup", startup_path) # expected via the `exit` and `quit` commands
return_code = se.code
import tokenize except BaseException:
with tokenize.open(startup_path) as f: # unexpected issue
startup_code = compile(f.read(), startup_path, "exec") console.showtraceback()
exec(startup_code, console.locals) console.write("Internal error, ")
return_code = 1
ps1 = getattr(sys, "ps1", ">>> ") else:
if CAN_USE_PYREPL: try:
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:
console.interact(banner="", exitmsg="") console.interact(banner="", exitmsg="")
finally: except SystemExit as se:
warnings.filterwarnings( return_code = se.code
'ignore', finally:
message=r'^coroutine .* was never awaited$', warnings.filterwarnings(
category=RuntimeWarning) 'ignore',
message=r'^coroutine .* was never awaited$',
category=RuntimeWarning)
loop.call_soon_threadsafe(loop.stop) loop.call_soon_threadsafe(loop.stop)
def interrupt(self) -> None:
if not CAN_USE_PYREPL:
return
from _pyrepl.simple_interact import _get_reader
r = _get_reader()
if r.threading_hook is not None:
r.threading_hook.add("") # type: ignore
if __name__ == '__main__': if __name__ == '__main__':
@ -198,9 +155,6 @@ def interrupt(self) -> None:
console = AsyncIOInteractiveConsole(repl_locals, loop) console = AsyncIOInteractiveConsole(repl_locals, loop)
repl_future = None
keyboard_interrupted = False
try: try:
import readline # NoQA import readline # NoQA
except ImportError: except ImportError:
@ -223,21 +177,6 @@ def interrupt(self) -> None:
completer = rlcompleter.Completer(console.locals) completer = rlcompleter.Completer(console.locals)
readline.set_completer(completer.complete) readline.set_completer(completer.complete)
repl_thread = REPLThread(name="Interactive thread") interact()
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
console.write('exiting asyncio REPL...\n') console.write('exiting asyncio REPL...\n')
sys.exit(return_code) sys.exit(return_code)

View file

@ -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.