Mercurial > repos > rliterman > csp2
comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/run.py @ 68:5028fdace37b
planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author | jpayne |
---|---|
date | Tue, 18 Mar 2025 16:23:26 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
67:0e9998148a16 | 68:5028fdace37b |
---|---|
1 """ idlelib.run | |
2 | |
3 Simplified, pyshell.ModifiedInterpreter spawns a subprocess with | |
4 f'''{sys.executable} -c "__import__('idlelib.run').run.main()"''' | |
5 '.run' is needed because __import__ returns idlelib, not idlelib.run. | |
6 """ | |
7 import functools | |
8 import io | |
9 import linecache | |
10 import queue | |
11 import sys | |
12 import textwrap | |
13 import time | |
14 import traceback | |
15 import _thread as thread | |
16 import threading | |
17 import warnings | |
18 | |
19 from idlelib import autocomplete # AutoComplete, fetch_encodings | |
20 from idlelib import calltip # Calltip | |
21 from idlelib import debugger_r # start_debugger | |
22 from idlelib import debugobj_r # remote_object_tree_item | |
23 from idlelib import iomenu # encoding | |
24 from idlelib import rpc # multiple objects | |
25 from idlelib import stackviewer # StackTreeItem | |
26 import __main__ | |
27 | |
28 import tkinter # Use tcl and, if startup fails, messagebox. | |
29 if not hasattr(sys.modules['idlelib.run'], 'firstrun'): | |
30 # Undo modifications of tkinter by idlelib imports; see bpo-25507. | |
31 for mod in ('simpledialog', 'messagebox', 'font', | |
32 'dialog', 'filedialog', 'commondialog', | |
33 'ttk'): | |
34 delattr(tkinter, mod) | |
35 del sys.modules['tkinter.' + mod] | |
36 # Avoid AttributeError if run again; see bpo-37038. | |
37 sys.modules['idlelib.run'].firstrun = False | |
38 | |
39 LOCALHOST = '127.0.0.1' | |
40 | |
41 | |
42 def idle_formatwarning(message, category, filename, lineno, line=None): | |
43 """Format warnings the IDLE way.""" | |
44 | |
45 s = "\nWarning (from warnings module):\n" | |
46 s += ' File \"%s\", line %s\n' % (filename, lineno) | |
47 if line is None: | |
48 line = linecache.getline(filename, lineno) | |
49 line = line.strip() | |
50 if line: | |
51 s += " %s\n" % line | |
52 s += "%s: %s\n" % (category.__name__, message) | |
53 return s | |
54 | |
55 def idle_showwarning_subproc( | |
56 message, category, filename, lineno, file=None, line=None): | |
57 """Show Idle-format warning after replacing warnings.showwarning. | |
58 | |
59 The only difference is the formatter called. | |
60 """ | |
61 if file is None: | |
62 file = sys.stderr | |
63 try: | |
64 file.write(idle_formatwarning( | |
65 message, category, filename, lineno, line)) | |
66 except OSError: | |
67 pass # the file (probably stderr) is invalid - this warning gets lost. | |
68 | |
69 _warnings_showwarning = None | |
70 | |
71 def capture_warnings(capture): | |
72 "Replace warning.showwarning with idle_showwarning_subproc, or reverse." | |
73 | |
74 global _warnings_showwarning | |
75 if capture: | |
76 if _warnings_showwarning is None: | |
77 _warnings_showwarning = warnings.showwarning | |
78 warnings.showwarning = idle_showwarning_subproc | |
79 else: | |
80 if _warnings_showwarning is not None: | |
81 warnings.showwarning = _warnings_showwarning | |
82 _warnings_showwarning = None | |
83 | |
84 capture_warnings(True) | |
85 tcl = tkinter.Tcl() | |
86 | |
87 def handle_tk_events(tcl=tcl): | |
88 """Process any tk events that are ready to be dispatched if tkinter | |
89 has been imported, a tcl interpreter has been created and tk has been | |
90 loaded.""" | |
91 tcl.eval("update") | |
92 | |
93 # Thread shared globals: Establish a queue between a subthread (which handles | |
94 # the socket) and the main thread (which runs user code), plus global | |
95 # completion, exit and interruptable (the main thread) flags: | |
96 | |
97 exit_now = False | |
98 quitting = False | |
99 interruptable = False | |
100 | |
101 def main(del_exitfunc=False): | |
102 """Start the Python execution server in a subprocess | |
103 | |
104 In the Python subprocess, RPCServer is instantiated with handlerclass | |
105 MyHandler, which inherits register/unregister methods from RPCHandler via | |
106 the mix-in class SocketIO. | |
107 | |
108 When the RPCServer 'server' is instantiated, the TCPServer initialization | |
109 creates an instance of run.MyHandler and calls its handle() method. | |
110 handle() instantiates a run.Executive object, passing it a reference to the | |
111 MyHandler object. That reference is saved as attribute rpchandler of the | |
112 Executive instance. The Executive methods have access to the reference and | |
113 can pass it on to entities that they command | |
114 (e.g. debugger_r.Debugger.start_debugger()). The latter, in turn, can | |
115 call MyHandler(SocketIO) register/unregister methods via the reference to | |
116 register and unregister themselves. | |
117 | |
118 """ | |
119 global exit_now | |
120 global quitting | |
121 global no_exitfunc | |
122 no_exitfunc = del_exitfunc | |
123 #time.sleep(15) # test subprocess not responding | |
124 try: | |
125 assert(len(sys.argv) > 1) | |
126 port = int(sys.argv[-1]) | |
127 except: | |
128 print("IDLE Subprocess: no IP port passed in sys.argv.", | |
129 file=sys.__stderr__) | |
130 return | |
131 | |
132 capture_warnings(True) | |
133 sys.argv[:] = [""] | |
134 sockthread = threading.Thread(target=manage_socket, | |
135 name='SockThread', | |
136 args=((LOCALHOST, port),)) | |
137 sockthread.daemon = True | |
138 sockthread.start() | |
139 while 1: | |
140 try: | |
141 if exit_now: | |
142 try: | |
143 exit() | |
144 except KeyboardInterrupt: | |
145 # exiting but got an extra KBI? Try again! | |
146 continue | |
147 try: | |
148 request = rpc.request_queue.get(block=True, timeout=0.05) | |
149 except queue.Empty: | |
150 request = None | |
151 # Issue 32207: calling handle_tk_events here adds spurious | |
152 # queue.Empty traceback to event handling exceptions. | |
153 if request: | |
154 seq, (method, args, kwargs) = request | |
155 ret = method(*args, **kwargs) | |
156 rpc.response_queue.put((seq, ret)) | |
157 else: | |
158 handle_tk_events() | |
159 except KeyboardInterrupt: | |
160 if quitting: | |
161 exit_now = True | |
162 continue | |
163 except SystemExit: | |
164 capture_warnings(False) | |
165 raise | |
166 except: | |
167 type, value, tb = sys.exc_info() | |
168 try: | |
169 print_exception() | |
170 rpc.response_queue.put((seq, None)) | |
171 except: | |
172 # Link didn't work, print same exception to __stderr__ | |
173 traceback.print_exception(type, value, tb, file=sys.__stderr__) | |
174 exit() | |
175 else: | |
176 continue | |
177 | |
178 def manage_socket(address): | |
179 for i in range(3): | |
180 time.sleep(i) | |
181 try: | |
182 server = MyRPCServer(address, MyHandler) | |
183 break | |
184 except OSError as err: | |
185 print("IDLE Subprocess: OSError: " + err.args[1] + | |
186 ", retrying....", file=sys.__stderr__) | |
187 socket_error = err | |
188 else: | |
189 print("IDLE Subprocess: Connection to " | |
190 "IDLE GUI failed, exiting.", file=sys.__stderr__) | |
191 show_socket_error(socket_error, address) | |
192 global exit_now | |
193 exit_now = True | |
194 return | |
195 server.handle_request() # A single request only | |
196 | |
197 def show_socket_error(err, address): | |
198 "Display socket error from manage_socket." | |
199 import tkinter | |
200 from tkinter.messagebox import showerror | |
201 root = tkinter.Tk() | |
202 fix_scaling(root) | |
203 root.withdraw() | |
204 showerror( | |
205 "Subprocess Connection Error", | |
206 f"IDLE's subprocess can't connect to {address[0]}:{address[1]}.\n" | |
207 f"Fatal OSError #{err.errno}: {err.strerror}.\n" | |
208 "See the 'Startup failure' section of the IDLE doc, online at\n" | |
209 "https://docs.python.org/3/library/idle.html#startup-failure", | |
210 parent=root) | |
211 root.destroy() | |
212 | |
213 def print_exception(): | |
214 import linecache | |
215 linecache.checkcache() | |
216 flush_stdout() | |
217 efile = sys.stderr | |
218 typ, val, tb = excinfo = sys.exc_info() | |
219 sys.last_type, sys.last_value, sys.last_traceback = excinfo | |
220 seen = set() | |
221 | |
222 def print_exc(typ, exc, tb): | |
223 seen.add(id(exc)) | |
224 context = exc.__context__ | |
225 cause = exc.__cause__ | |
226 if cause is not None and id(cause) not in seen: | |
227 print_exc(type(cause), cause, cause.__traceback__) | |
228 print("\nThe above exception was the direct cause " | |
229 "of the following exception:\n", file=efile) | |
230 elif (context is not None and | |
231 not exc.__suppress_context__ and | |
232 id(context) not in seen): | |
233 print_exc(type(context), context, context.__traceback__) | |
234 print("\nDuring handling of the above exception, " | |
235 "another exception occurred:\n", file=efile) | |
236 if tb: | |
237 tbe = traceback.extract_tb(tb) | |
238 print('Traceback (most recent call last):', file=efile) | |
239 exclude = ("run.py", "rpc.py", "threading.py", "queue.py", | |
240 "debugger_r.py", "bdb.py") | |
241 cleanup_traceback(tbe, exclude) | |
242 traceback.print_list(tbe, file=efile) | |
243 lines = traceback.format_exception_only(typ, exc) | |
244 for line in lines: | |
245 print(line, end='', file=efile) | |
246 | |
247 print_exc(typ, val, tb) | |
248 | |
249 def cleanup_traceback(tb, exclude): | |
250 "Remove excluded traces from beginning/end of tb; get cached lines" | |
251 orig_tb = tb[:] | |
252 while tb: | |
253 for rpcfile in exclude: | |
254 if tb[0][0].count(rpcfile): | |
255 break # found an exclude, break for: and delete tb[0] | |
256 else: | |
257 break # no excludes, have left RPC code, break while: | |
258 del tb[0] | |
259 while tb: | |
260 for rpcfile in exclude: | |
261 if tb[-1][0].count(rpcfile): | |
262 break | |
263 else: | |
264 break | |
265 del tb[-1] | |
266 if len(tb) == 0: | |
267 # exception was in IDLE internals, don't prune! | |
268 tb[:] = orig_tb[:] | |
269 print("** IDLE Internal Exception: ", file=sys.stderr) | |
270 rpchandler = rpc.objecttable['exec'].rpchandler | |
271 for i in range(len(tb)): | |
272 fn, ln, nm, line = tb[i] | |
273 if nm == '?': | |
274 nm = "-toplevel-" | |
275 if not line and fn.startswith("<pyshell#"): | |
276 line = rpchandler.remotecall('linecache', 'getline', | |
277 (fn, ln), {}) | |
278 tb[i] = fn, ln, nm, line | |
279 | |
280 def flush_stdout(): | |
281 """XXX How to do this now?""" | |
282 | |
283 def exit(): | |
284 """Exit subprocess, possibly after first clearing exit functions. | |
285 | |
286 If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any | |
287 functions registered with atexit will be removed before exiting. | |
288 (VPython support) | |
289 | |
290 """ | |
291 if no_exitfunc: | |
292 import atexit | |
293 atexit._clear() | |
294 capture_warnings(False) | |
295 sys.exit(0) | |
296 | |
297 | |
298 def fix_scaling(root): | |
299 """Scale fonts on HiDPI displays.""" | |
300 import tkinter.font | |
301 scaling = float(root.tk.call('tk', 'scaling')) | |
302 if scaling > 1.4: | |
303 for name in tkinter.font.names(root): | |
304 font = tkinter.font.Font(root=root, name=name, exists=True) | |
305 size = int(font['size']) | |
306 if size < 0: | |
307 font['size'] = round(-0.75*size) | |
308 | |
309 | |
310 def fixdoc(fun, text): | |
311 tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else '' | |
312 fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text)) | |
313 | |
314 RECURSIONLIMIT_DELTA = 30 | |
315 | |
316 def install_recursionlimit_wrappers(): | |
317 """Install wrappers to always add 30 to the recursion limit.""" | |
318 # see: bpo-26806 | |
319 | |
320 @functools.wraps(sys.setrecursionlimit) | |
321 def setrecursionlimit(*args, **kwargs): | |
322 # mimic the original sys.setrecursionlimit()'s input handling | |
323 if kwargs: | |
324 raise TypeError( | |
325 "setrecursionlimit() takes no keyword arguments") | |
326 try: | |
327 limit, = args | |
328 except ValueError: | |
329 raise TypeError(f"setrecursionlimit() takes exactly one " | |
330 f"argument ({len(args)} given)") | |
331 if not limit > 0: | |
332 raise ValueError( | |
333 "recursion limit must be greater or equal than 1") | |
334 | |
335 return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA) | |
336 | |
337 fixdoc(setrecursionlimit, f"""\ | |
338 This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible | |
339 uninterruptible loops.""") | |
340 | |
341 @functools.wraps(sys.getrecursionlimit) | |
342 def getrecursionlimit(): | |
343 return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA | |
344 | |
345 fixdoc(getrecursionlimit, f"""\ | |
346 This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate | |
347 for the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.""") | |
348 | |
349 # add the delta to the default recursion limit, to compensate | |
350 sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA) | |
351 | |
352 sys.setrecursionlimit = setrecursionlimit | |
353 sys.getrecursionlimit = getrecursionlimit | |
354 | |
355 | |
356 def uninstall_recursionlimit_wrappers(): | |
357 """Uninstall the recursion limit wrappers from the sys module. | |
358 | |
359 IDLE only uses this for tests. Users can import run and call | |
360 this to remove the wrapping. | |
361 """ | |
362 if ( | |
363 getattr(sys.setrecursionlimit, '__wrapped__', None) and | |
364 getattr(sys.getrecursionlimit, '__wrapped__', None) | |
365 ): | |
366 sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__ | |
367 sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__ | |
368 sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA) | |
369 | |
370 | |
371 class MyRPCServer(rpc.RPCServer): | |
372 | |
373 def handle_error(self, request, client_address): | |
374 """Override RPCServer method for IDLE | |
375 | |
376 Interrupt the MainThread and exit server if link is dropped. | |
377 | |
378 """ | |
379 global quitting | |
380 try: | |
381 raise | |
382 except SystemExit: | |
383 raise | |
384 except EOFError: | |
385 global exit_now | |
386 exit_now = True | |
387 thread.interrupt_main() | |
388 except: | |
389 erf = sys.__stderr__ | |
390 print('\n' + '-'*40, file=erf) | |
391 print('Unhandled server exception!', file=erf) | |
392 print('Thread: %s' % threading.current_thread().name, file=erf) | |
393 print('Client Address: ', client_address, file=erf) | |
394 print('Request: ', repr(request), file=erf) | |
395 traceback.print_exc(file=erf) | |
396 print('\n*** Unrecoverable, server exiting!', file=erf) | |
397 print('-'*40, file=erf) | |
398 quitting = True | |
399 thread.interrupt_main() | |
400 | |
401 | |
402 # Pseudofiles for shell-remote communication (also used in pyshell) | |
403 | |
404 class StdioFile(io.TextIOBase): | |
405 | |
406 def __init__(self, shell, tags, encoding='utf-8', errors='strict'): | |
407 self.shell = shell | |
408 self.tags = tags | |
409 self._encoding = encoding | |
410 self._errors = errors | |
411 | |
412 @property | |
413 def encoding(self): | |
414 return self._encoding | |
415 | |
416 @property | |
417 def errors(self): | |
418 return self._errors | |
419 | |
420 @property | |
421 def name(self): | |
422 return '<%s>' % self.tags | |
423 | |
424 def isatty(self): | |
425 return True | |
426 | |
427 | |
428 class StdOutputFile(StdioFile): | |
429 | |
430 def writable(self): | |
431 return True | |
432 | |
433 def write(self, s): | |
434 if self.closed: | |
435 raise ValueError("write to closed file") | |
436 s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors) | |
437 return self.shell.write(s, self.tags) | |
438 | |
439 | |
440 class StdInputFile(StdioFile): | |
441 _line_buffer = '' | |
442 | |
443 def readable(self): | |
444 return True | |
445 | |
446 def read(self, size=-1): | |
447 if self.closed: | |
448 raise ValueError("read from closed file") | |
449 if size is None: | |
450 size = -1 | |
451 elif not isinstance(size, int): | |
452 raise TypeError('must be int, not ' + type(size).__name__) | |
453 result = self._line_buffer | |
454 self._line_buffer = '' | |
455 if size < 0: | |
456 while True: | |
457 line = self.shell.readline() | |
458 if not line: break | |
459 result += line | |
460 else: | |
461 while len(result) < size: | |
462 line = self.shell.readline() | |
463 if not line: break | |
464 result += line | |
465 self._line_buffer = result[size:] | |
466 result = result[:size] | |
467 return result | |
468 | |
469 def readline(self, size=-1): | |
470 if self.closed: | |
471 raise ValueError("read from closed file") | |
472 if size is None: | |
473 size = -1 | |
474 elif not isinstance(size, int): | |
475 raise TypeError('must be int, not ' + type(size).__name__) | |
476 line = self._line_buffer or self.shell.readline() | |
477 if size < 0: | |
478 size = len(line) | |
479 eol = line.find('\n', 0, size) | |
480 if eol >= 0: | |
481 size = eol + 1 | |
482 self._line_buffer = line[size:] | |
483 return line[:size] | |
484 | |
485 def close(self): | |
486 self.shell.close() | |
487 | |
488 | |
489 class MyHandler(rpc.RPCHandler): | |
490 | |
491 def handle(self): | |
492 """Override base method""" | |
493 executive = Executive(self) | |
494 self.register("exec", executive) | |
495 self.console = self.get_remote_proxy("console") | |
496 sys.stdin = StdInputFile(self.console, "stdin", | |
497 iomenu.encoding, iomenu.errors) | |
498 sys.stdout = StdOutputFile(self.console, "stdout", | |
499 iomenu.encoding, iomenu.errors) | |
500 sys.stderr = StdOutputFile(self.console, "stderr", | |
501 iomenu.encoding, "backslashreplace") | |
502 | |
503 sys.displayhook = rpc.displayhook | |
504 # page help() text to shell. | |
505 import pydoc # import must be done here to capture i/o binding | |
506 pydoc.pager = pydoc.plainpager | |
507 | |
508 # Keep a reference to stdin so that it won't try to exit IDLE if | |
509 # sys.stdin gets changed from within IDLE's shell. See issue17838. | |
510 self._keep_stdin = sys.stdin | |
511 | |
512 install_recursionlimit_wrappers() | |
513 | |
514 self.interp = self.get_remote_proxy("interp") | |
515 rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) | |
516 | |
517 def exithook(self): | |
518 "override SocketIO method - wait for MainThread to shut us down" | |
519 time.sleep(10) | |
520 | |
521 def EOFhook(self): | |
522 "Override SocketIO method - terminate wait on callback and exit thread" | |
523 global quitting | |
524 quitting = True | |
525 thread.interrupt_main() | |
526 | |
527 def decode_interrupthook(self): | |
528 "interrupt awakened thread" | |
529 global quitting | |
530 quitting = True | |
531 thread.interrupt_main() | |
532 | |
533 | |
534 class Executive(object): | |
535 | |
536 def __init__(self, rpchandler): | |
537 self.rpchandler = rpchandler | |
538 self.locals = __main__.__dict__ | |
539 self.calltip = calltip.Calltip() | |
540 self.autocomplete = autocomplete.AutoComplete() | |
541 | |
542 def runcode(self, code): | |
543 global interruptable | |
544 try: | |
545 self.usr_exc_info = None | |
546 interruptable = True | |
547 try: | |
548 exec(code, self.locals) | |
549 finally: | |
550 interruptable = False | |
551 except SystemExit as e: | |
552 if e.args: # SystemExit called with an argument. | |
553 ob = e.args[0] | |
554 if not isinstance(ob, (type(None), int)): | |
555 print('SystemExit: ' + str(ob), file=sys.stderr) | |
556 # Return to the interactive prompt. | |
557 except: | |
558 self.usr_exc_info = sys.exc_info() | |
559 if quitting: | |
560 exit() | |
561 print_exception() | |
562 jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>") | |
563 if jit: | |
564 self.rpchandler.interp.open_remote_stack_viewer() | |
565 else: | |
566 flush_stdout() | |
567 | |
568 def interrupt_the_server(self): | |
569 if interruptable: | |
570 thread.interrupt_main() | |
571 | |
572 def start_the_debugger(self, gui_adap_oid): | |
573 return debugger_r.start_debugger(self.rpchandler, gui_adap_oid) | |
574 | |
575 def stop_the_debugger(self, idb_adap_oid): | |
576 "Unregister the Idb Adapter. Link objects and Idb then subject to GC" | |
577 self.rpchandler.unregister(idb_adap_oid) | |
578 | |
579 def get_the_calltip(self, name): | |
580 return self.calltip.fetch_tip(name) | |
581 | |
582 def get_the_completion_list(self, what, mode): | |
583 return self.autocomplete.fetch_completions(what, mode) | |
584 | |
585 def stackviewer(self, flist_oid=None): | |
586 if self.usr_exc_info: | |
587 typ, val, tb = self.usr_exc_info | |
588 else: | |
589 return None | |
590 flist = None | |
591 if flist_oid is not None: | |
592 flist = self.rpchandler.get_remote_proxy(flist_oid) | |
593 while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]: | |
594 tb = tb.tb_next | |
595 sys.last_type = typ | |
596 sys.last_value = val | |
597 item = stackviewer.StackTreeItem(flist, tb) | |
598 return debugobj_r.remote_object_tree_item(item) | |
599 | |
600 | |
601 if __name__ == '__main__': | |
602 from unittest import main | |
603 main('idlelib.idle_test.test_run', verbosity=2) | |
604 | |
605 capture_warnings(False) # Make sure turned off; see bpo-18081. |