11import ast
22import asyncio
3- import code
43import concurrent .futures
4+ import contextvars
55import inspect
6+ import os
7+ import site
68import sys
79import threading
810import types
911import warnings
1012
13+ from _colorize import can_colorize , ANSIColors # type: ignore[import-not-found]
14+ from _pyrepl .console import InteractiveColoredConsole
15+
1116from . import futures
1217
1318
14- class AsyncIOInteractiveConsole (code . InteractiveConsole ):
19+ class AsyncIOInteractiveConsole (InteractiveColoredConsole ):
1520
1621 def __init__ (self , locals , loop ):
17- super ().__init__ (locals )
22+ super ().__init__ (locals , filename = "<stdin>" )
1823 self .compile .compiler .flags |= ast .PyCF_ALLOW_TOP_LEVEL_AWAIT
1924
2025 self .loop = loop
26+ self .context = contextvars .copy_context ()
2127
2228 def runcode (self , code ):
29+ global return_code
2330 future = concurrent .futures .Future ()
2431
2532 def callback ():
33+ global return_code
2634 global repl_future
27- global repl_future_interrupted
35+ global keyboard_interrupted
2836
2937 repl_future = None
30- repl_future_interrupted = False
38+ keyboard_interrupted = False
3139
3240 func = types .FunctionType (code , self .locals )
3341 try :
3442 coro = func ()
35- except SystemExit :
36- raise
43+ except SystemExit as se :
44+ return_code = se .code
45+ self .loop .stop ()
46+ return
3747 except KeyboardInterrupt as ex :
38- repl_future_interrupted = True
48+ keyboard_interrupted = True
3949 future .set_exception (ex )
4050 return
4151 except BaseException as ex :
@@ -47,39 +57,71 @@ def callback():
4757 return
4858
4959 try :
50- repl_future = self .loop .create_task (coro )
60+ repl_future = self .loop .create_task (coro , context = self . context )
5161 futures ._chain_future (repl_future , future )
5262 except BaseException as exc :
5363 future .set_exception (exc )
5464
55- loop .call_soon_threadsafe (callback )
65+ self . loop .call_soon_threadsafe (callback , context = self . context )
5666
5767 try :
5868 return future .result ()
59- except SystemExit :
60- raise
69+ except SystemExit as se :
70+ return_code = se .code
71+ self .loop .stop ()
72+ return
6173 except BaseException :
62- if repl_future_interrupted :
63- self .write ("\n KeyboardInterrupt\n " )
74+ if keyboard_interrupted :
75+ if not CAN_USE_PYREPL :
76+ self .write ("\n KeyboardInterrupt\n " )
6477 else :
6578 self .showtraceback ()
66-
79+ return self . STATEMENT_FAILED
6780
6881class REPLThread (threading .Thread ):
6982
7083 def run (self ):
84+ global return_code
85+
7186 try :
7287 banner = (
7388 f'asyncio REPL { sys .version } on { sys .platform } \n '
7489 f'Use "await" directly instead of "asyncio.run()".\n '
7590 f'Type "help", "copyright", "credits" or "license" '
7691 f'for more information.\n '
77- f'{ getattr (sys , "ps1" , ">>> " )} import asyncio'
7892 )
7993
80- console .interact (
81- banner = banner ,
82- exitmsg = 'exiting asyncio REPL...' )
94+ console .write (banner )
95+
96+ if startup_path := os .getenv ("PYTHONSTARTUP" ):
97+ sys .audit ("cpython.run_startup" , startup_path )
98+
99+ import tokenize
100+ with tokenize .open (startup_path ) as f :
101+ startup_code = compile (f .read (), startup_path , "exec" )
102+ exec (startup_code , console .locals )
103+
104+ ps1 = getattr (sys , "ps1" , ">>> " )
105+ if can_colorize () and CAN_USE_PYREPL :
106+ ps1 = f"{ ANSIColors .BOLD_MAGENTA } { ps1 } { ANSIColors .RESET } "
107+ console .write (f"{ ps1 } import asyncio\n " )
108+
109+ if CAN_USE_PYREPL :
110+ from _pyrepl .simple_interact import (
111+ run_multiline_interactive_console ,
112+ )
113+ try :
114+ run_multiline_interactive_console (console )
115+ except SystemExit :
116+ # expected via the `exit` and `quit` commands
117+ pass
118+ except BaseException :
119+ # unexpected issue
120+ console .showtraceback ()
121+ console .write ("Internal error, " )
122+ return_code = 1
123+ else :
124+ console .interact (banner = "" , exitmsg = "" )
83125 finally :
84126 warnings .filterwarnings (
85127 'ignore' ,
@@ -88,8 +130,25 @@ def run(self):
88130
89131 loop .call_soon_threadsafe (loop .stop )
90132
133+ def interrupt (self ) -> None :
134+ if not CAN_USE_PYREPL :
135+ return
136+
137+ from _pyrepl .simple_interact import _get_reader
138+ r = _get_reader ()
139+ if r .threading_hook is not None :
140+ r .threading_hook .add ("" ) # type: ignore
141+
91142
92143if __name__ == '__main__' :
144+ sys .audit ("cpython.run_stdin" )
145+
146+ if os .getenv ('PYTHON_BASIC_REPL' ):
147+ CAN_USE_PYREPL = False
148+ else :
149+ from _pyrepl .main import CAN_USE_PYREPL
150+
151+ return_code = 0
93152 loop = asyncio .new_event_loop ()
94153 asyncio .set_event_loop (loop )
95154
@@ -102,24 +161,45 @@ def run(self):
102161 console = AsyncIOInteractiveConsole (repl_locals , loop )
103162
104163 repl_future = None
105- repl_future_interrupted = False
164+ keyboard_interrupted = False
106165
107166 try :
108167 import readline # NoQA
109168 except ImportError :
110- pass
169+ readline = None
170+
171+ interactive_hook = getattr (sys , "__interactivehook__" , None )
111172
112- repl_thread = REPLThread ()
173+ if interactive_hook is not None :
174+ sys .audit ("cpython.run_interactivehook" , interactive_hook )
175+ interactive_hook ()
176+
177+ if interactive_hook is site .register_readline :
178+ # Fix the completer function to use the interactive console locals
179+ try :
180+ import rlcompleter
181+ except :
182+ pass
183+ else :
184+ if readline is not None :
185+ completer = rlcompleter .Completer (console .locals )
186+ readline .set_completer (completer .complete )
187+
188+ repl_thread = REPLThread (name = "Interactive thread" )
113189 repl_thread .daemon = True
114190 repl_thread .start ()
115191
116192 while True :
117193 try :
118194 loop .run_forever ()
119195 except KeyboardInterrupt :
196+ keyboard_interrupted = True
120197 if repl_future and not repl_future .done ():
121198 repl_future .cancel ()
122- repl_future_interrupted = True
199+ repl_thread . interrupt ()
123200 continue
124201 else :
125202 break
203+
204+ console .write ('exiting asyncio REPL...\n ' )
205+ sys .exit (return_code )
0 commit comments