From 16cefb0bc7b05c08caf08525398ff178c35dece4 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Mon, 27 May 2019 13:42:29 +0200 Subject: [PATCH] bpo-37028: asyncio REPL; activated via 'python -m asyncio'. (GH-13472) This makes it easy to play with asyncio APIs with simply using async/await in the REPL. --- Lib/asyncio/__main__.py | 125 ++++++++++++++++++ .../2019-05-23-18-57-34.bpo-37028.Vse6Pj.rst | 1 + 2 files changed, 126 insertions(+) create mode 100644 Lib/asyncio/__main__.py create mode 100644 Misc/NEWS.d/next/Library/2019-05-23-18-57-34.bpo-37028.Vse6Pj.rst diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py new file mode 100644 index 00000000000..18bb87a5bc4 --- /dev/null +++ b/Lib/asyncio/__main__.py @@ -0,0 +1,125 @@ +import ast +import asyncio +import code +import concurrent.futures +import inspect +import sys +import threading +import types +import warnings + +from . import futures + + +class AsyncIOInteractiveConsole(code.InteractiveConsole): + + def __init__(self, locals, loop): + super().__init__(locals) + self.compile.compiler.flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT + + self.loop = loop + + def runcode(self, code): + future = concurrent.futures.Future() + + def callback(): + global repl_future + global repl_future_interrupted + + repl_future = None + repl_future_interrupted = False + + func = types.FunctionType(code, self.locals) + try: + coro = func() + except SystemExit: + raise + except KeyboardInterrupt as ex: + repl_future_interrupted = True + future.set_exception(ex) + return + except BaseException as ex: + future.set_exception(ex) + return + + if not inspect.iscoroutine(coro): + future.set_result(coro) + return + + try: + repl_future = self.loop.create_task(coro) + futures._chain_future(repl_future, future) + except BaseException as exc: + future.set_exception(exc) + + loop.call_soon_threadsafe(callback) + + try: + return future.result() + except SystemExit: + raise + except BaseException: + if repl_future_interrupted: + self.write("\nKeyboardInterrupt\n") + else: + self.showtraceback() + + +class REPLThread(threading.Thread): + + def run(self): + 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' + f'{getattr(sys, "ps1", ">>> ")}import asyncio' + ) + + console.interact( + banner=banner, + exitmsg='exiting asyncio REPL...') + finally: + warnings.filterwarnings( + 'ignore', + message=r'^coroutine .* was never awaited$', + category=RuntimeWarning) + + loop.call_soon_threadsafe(loop.stop) + + +if __name__ == '__main__': + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + repl_locals = {'asyncio': asyncio} + for key in {'__name__', '__package__', + '__loader__', '__spec__', + '__builtins__', '__file__'}: + repl_locals[key] = locals()[key] + + console = AsyncIOInteractiveConsole(repl_locals, loop) + + repl_future = None + repl_future_interrupted = False + + try: + import readline # NoQA + except ImportError: + pass + + repl_thread = REPLThread() + repl_thread.daemon = True + repl_thread.start() + + while True: + try: + loop.run_forever() + except KeyboardInterrupt: + if repl_future and not repl_future.done(): + repl_future.cancel() + repl_future_interrupted = True + continue + else: + break diff --git a/Misc/NEWS.d/next/Library/2019-05-23-18-57-34.bpo-37028.Vse6Pj.rst b/Misc/NEWS.d/next/Library/2019-05-23-18-57-34.bpo-37028.Vse6Pj.rst new file mode 100644 index 00000000000..d9db21fb6f3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-05-23-18-57-34.bpo-37028.Vse6Pj.rst @@ -0,0 +1 @@ +Implement asyncio REPL