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 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):
@ -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
return coro
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:
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")
if CAN_USE_PYREPL:
from _pyrepl.simple_interact import (
run_multiline_interactive_console,
)
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:
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)
except SystemExit as se:
return_code = se.code
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
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__':
@ -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:
@ -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)

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.