import contextlib
import functools
import io
import linecache
import queue
import sys
import textwrap
import time
import traceback
import _thread as thread
import threading
import warnings
import idlelib from idlelib import autocomplete from idlelib import calltip from idlelib import debugger_r from idlelib import debugobj_r from idlelib import iomenu from idlelib import rpc from idlelib import stackviewer import __main__
import tkinter if not hasattr(sys.modules['idlelib.run'], 'firstrun'):
for mod in ('simpledialog', 'messagebox', 'font',
'dialog', 'filedialog', 'commondialog',
'ttk'):
delattr(tkinter, mod)
del sys.modules['tkinter.' + mod]
sys.modules['idlelib.run'].firstrun = False
LOCALHOST = '127.0.0.1'
try:
eof = 'Ctrl-D (end-of-file)'
exit.eof = eof
quit.eof = eof
except NameError: pass
def idle_formatwarning(message, category, filename, lineno, line=None):
s = "\nWarning (from warnings module):\n"
s += f' File \"{filename}\", line {lineno}\n'
if line is None:
line = linecache.getline(filename, lineno)
line = line.strip()
if line:
s += " %s\n" % line
s += f"{category.__name__}: {message}\n"
return s
def idle_showwarning_subproc(
message, category, filename, lineno, file=None, line=None):
if file is None:
file = sys.stderr
try:
file.write(idle_formatwarning(
message, category, filename, lineno, line))
except OSError:
pass
_warnings_showwarning = None
def capture_warnings(capture):
"Replace warning.showwarning with idle_showwarning_subproc, or reverse."
global _warnings_showwarning
if capture:
if _warnings_showwarning is None:
_warnings_showwarning = warnings.showwarning
warnings.showwarning = idle_showwarning_subproc
else:
if _warnings_showwarning is not None:
warnings.showwarning = _warnings_showwarning
_warnings_showwarning = None
capture_warnings(True)
if idlelib.testing:
def handle_tk_events():
pass
else:
tcl = tkinter.Tcl()
def handle_tk_events(tcl=tcl):
tcl.eval("update")
exit_now = False
quitting = False
interruptible = False
def main(del_exitfunc=False):
global exit_now
global quitting
global no_exitfunc
no_exitfunc = del_exitfunc
try:
assert(len(sys.argv) > 1)
port = int(sys.argv[-1])
except:
print("IDLE Subprocess: no IP port passed in sys.argv.",
file=sys.__stderr__)
return
capture_warnings(True)
sys.argv[:] = [""]
threading.Thread(target=manage_socket,
name='SockThread',
args=((LOCALHOST, port),),
daemon=True,
).start()
while True:
try:
if exit_now:
try:
exit()
except KeyboardInterrupt:
continue
try:
request = rpc.request_queue.get(block=True, timeout=0.05)
except queue.Empty:
request = None
if request:
seq, (method, args, kwargs) = request
ret = method(*args, **kwargs)
rpc.response_queue.put((seq, ret))
else:
handle_tk_events()
except KeyboardInterrupt:
if quitting:
exit_now = True
continue
except SystemExit:
capture_warnings(False)
raise
except:
type, value, tb = sys.exc_info()
try:
print_exception()
rpc.response_queue.put((seq, None))
except:
traceback.print_exception(type, value, tb, file=sys.__stderr__)
exit()
else:
continue
def manage_socket(address):
for i in range(3):
time.sleep(i)
try:
server = MyRPCServer(address, MyHandler)
break
except OSError as err:
print("IDLE Subprocess: OSError: " + err.args[1] +
", retrying....", file=sys.__stderr__)
socket_error = err
else:
print("IDLE Subprocess: Connection to "
"IDLE GUI failed, exiting.", file=sys.__stderr__)
show_socket_error(socket_error, address)
global exit_now
exit_now = True
return
server.handle_request()
def show_socket_error(err, address):
"Display socket error from manage_socket."
import tkinter
from tkinter.messagebox import showerror
root = tkinter.Tk()
fix_scaling(root)
root.withdraw()
showerror(
"Subprocess Connection Error",
f"IDLE's subprocess can't connect to {address[0]}:{address[1]}.\n"
f"Fatal OSError #{err.errno}: {err.strerror}.\n"
"See the 'Startup failure' section of the IDLE doc, online at\n"
"https://docs.python.org/3/library/idle.html#startup-failure",
parent=root)
root.destroy()
def get_message_lines(typ, exc, tb):
"Return line composing the exception message."
if typ in (AttributeError, NameError):
err = io.StringIO()
with contextlib.redirect_stderr(err):
sys.__excepthook__(typ, exc, tb)
return [err.getvalue().split("\n")[-2] + "\n"]
else:
return traceback.format_exception_only(typ, exc)
def print_exception():
import linecache
linecache.checkcache()
flush_stdout()
efile = sys.stderr
typ, val, tb = excinfo = sys.exc_info()
sys.last_type, sys.last_value, sys.last_traceback = excinfo
sys.last_exc = val
seen = set()
def print_exc(typ, exc, tb):
seen.add(id(exc))
context = exc.__context__
cause = exc.__cause__
if cause is not None and id(cause) not in seen:
print_exc(type(cause), cause, cause.__traceback__)
print("\nThe above exception was the direct cause "
"of the following exception:\n", file=efile)
elif (context is not None and
not exc.__suppress_context__ and
id(context) not in seen):
print_exc(type(context), context, context.__traceback__)
print("\nDuring handling of the above exception, "
"another exception occurred:\n", file=efile)
if tb:
tbe = traceback.extract_tb(tb)
print('Traceback (most recent call last):', file=efile)
exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
"debugger_r.py", "bdb.py")
cleanup_traceback(tbe, exclude)
traceback.print_list(tbe, file=efile)
lines = get_message_lines(typ, exc, tb)
for line in lines:
print(line, end='', file=efile)
print_exc(typ, val, tb)
def cleanup_traceback(tb, exclude):
"Remove excluded traces from beginning/end of tb; get cached lines"
orig_tb = tb[:]
while tb:
for rpcfile in exclude:
if tb[0][0].count(rpcfile):
break else:
break del tb[0]
while tb:
for rpcfile in exclude:
if tb[-1][0].count(rpcfile):
break
else:
break
del tb[-1]
if len(tb) == 0:
tb[:] = orig_tb[:]
print("** IDLE Internal Exception: ", file=sys.stderr)
rpchandler = rpc.objecttable['exec'].rpchandler
for i in range(len(tb)):
fn, ln, nm, line = tb[i]
if nm == '?':
nm = "-toplevel-"
if not line and fn.startswith("<pyshell#"):
line = rpchandler.remotecall('linecache', 'getline',
(fn, ln), {})
tb[i] = fn, ln, nm, line
def flush_stdout():
def exit():
if no_exitfunc:
import atexit
atexit._clear()
capture_warnings(False)
sys.exit(0)
def fix_scaling(root):
import tkinter.font
scaling = float(root.tk.call('tk', 'scaling'))
if scaling > 1.4:
for name in tkinter.font.names(root):
font = tkinter.font.Font(root=root, name=name, exists=True)
size = int(font['size'])
if size < 0:
font['size'] = round(-0.75*size)
def fixdoc(fun, text):
tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else ''
fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text))
RECURSIONLIMIT_DELTA = 30
def install_recursionlimit_wrappers():
@functools.wraps(sys.setrecursionlimit)
def setrecursionlimit(*args, **kwargs):
if kwargs:
raise TypeError(
"setrecursionlimit() takes no keyword arguments")
try:
limit, = args
except ValueError:
raise TypeError(f"setrecursionlimit() takes exactly one "
f"argument ({len(args)} given)")
if not limit > 0:
raise ValueError(
"recursion limit must be greater or equal than 1")
return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA)
fixdoc(setrecursionlimit, f"""\
This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible
uninterruptible loops.""")
@functools.wraps(sys.getrecursionlimit)
def getrecursionlimit():
return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA
fixdoc(getrecursionlimit, f"""\
This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate
for the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.""")
sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA)
sys.setrecursionlimit = setrecursionlimit
sys.getrecursionlimit = getrecursionlimit
def uninstall_recursionlimit_wrappers():
if (
getattr(sys.setrecursionlimit, '__wrapped__', None) and
getattr(sys.getrecursionlimit, '__wrapped__', None)
):
sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__
sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__
sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA)
class MyRPCServer(rpc.RPCServer):
def handle_error(self, request, client_address):
global quitting
try:
raise
except SystemExit:
raise
except EOFError:
global exit_now
exit_now = True
thread.interrupt_main()
except:
erf = sys.__stderr__
print(textwrap.dedent(f"""
{'-'*40}
Unhandled exception in user code execution server!'
Thread: {threading.current_thread().name}
IDLE Client Address: {client_address}
Request: {request!r}
"""), file=erf)
traceback.print_exc(limit=-20, file=erf)
print(textwrap.dedent(f"""
*** Unrecoverable, server exiting!
Users should never see this message; it is likely transient.
If this recurs, report this with a copy of the message
and an explanation of how to make it repeat.
{'-'*40}"""), file=erf)
quitting = True
thread.interrupt_main()
class StdioFile(io.TextIOBase):
def __init__(self, shell, tags, encoding='utf-8', errors='strict'):
self.shell = shell
self.shell._RPCProxy__attributes = {'width': 1}
self.tags = tags
self._encoding = encoding
self._errors = errors
@property
def encoding(self):
return self._encoding
@property
def errors(self):
return self._errors
@property
def name(self):
return '<%s>' % self.tags
def isatty(self):
return True
class StdOutputFile(StdioFile):
def writable(self):
return True
def write(self, s):
if self.closed:
raise ValueError("write to closed file")
s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors)
return self.shell.write(s, self.tags)
class StdInputFile(StdioFile):
_line_buffer = ''
def readable(self):
return True
def read(self, size=-1):
if self.closed:
raise ValueError("read from closed file")
if size is None:
size = -1
elif not isinstance(size, int):
raise TypeError('must be int, not ' + type(size).__name__)
result = self._line_buffer
self._line_buffer = ''
if size < 0:
while line := self.shell.readline():
result += line
else:
while len(result) < size:
line = self.shell.readline()
if not line: break
result += line
self._line_buffer = result[size:]
result = result[:size]
return result
def readline(self, size=-1):
if self.closed:
raise ValueError("read from closed file")
if size is None:
size = -1
elif not isinstance(size, int):
raise TypeError('must be int, not ' + type(size).__name__)
line = self._line_buffer or self.shell.readline()
if size < 0:
size = len(line)
eol = line.find('\n', 0, size)
if eol >= 0:
size = eol + 1
self._line_buffer = line[size:]
return line[:size]
def close(self):
self.shell.close()
class MyHandler(rpc.RPCHandler):
def handle(self):
executive = Executive(self)
self.register("exec", executive)
self.console = self.get_remote_proxy("console")
sys.stdin = StdInputFile(self.console, "stdin",
iomenu.encoding, iomenu.errors)
sys.stdout = StdOutputFile(self.console, "stdout",
iomenu.encoding, iomenu.errors)
sys.stderr = StdOutputFile(self.console, "stderr",
iomenu.encoding, "backslashreplace")
sys.displayhook = rpc.displayhook
import pydoc pydoc.pager = pydoc.plainpager
self._keep_stdin = sys.stdin
install_recursionlimit_wrappers()
self.interp = self.get_remote_proxy("interp")
rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
def exithook(self):
"override SocketIO method - wait for MainThread to shut us down"
time.sleep(10)
def EOFhook(self):
"Override SocketIO method - terminate wait on callback and exit thread"
global quitting
quitting = True
thread.interrupt_main()
def decode_interrupthook(self):
"interrupt awakened thread"
global quitting
quitting = True
thread.interrupt_main()
class Executive:
def __init__(self, rpchandler):
self.rpchandler = rpchandler
if idlelib.testing is False:
self.locals = __main__.__dict__
self.calltip = calltip.Calltip()
self.autocomplete = autocomplete.AutoComplete()
else:
self.locals = {}
def runcode(self, code):
global interruptible
try:
self.user_exc_info = None
interruptible = True
try:
exec(code, self.locals)
finally:
interruptible = False
except SystemExit as e:
if e.args: ob = e.args[0]
if not isinstance(ob, (type(None), int)):
print('SystemExit: ' + str(ob), file=sys.stderr)
except:
self.user_exc_info = sys.exc_info() if quitting:
exit()
if sys.excepthook is sys.__excepthook__:
print_exception()
else:
try:
sys.excepthook(*self.user_exc_info)
except:
self.user_exc_info = sys.exc_info() print_exception()
jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>")
if jit:
self.rpchandler.interp.open_remote_stack_viewer()
else:
flush_stdout()
def interrupt_the_server(self):
if interruptible:
thread.interrupt_main()
def start_the_debugger(self, gui_adap_oid):
return debugger_r.start_debugger(self.rpchandler, gui_adap_oid)
def stop_the_debugger(self, idb_adap_oid):
"Unregister the Idb Adapter. Link objects and Idb then subject to GC"
self.rpchandler.unregister(idb_adap_oid)
def get_the_calltip(self, name):
return self.calltip.fetch_tip(name)
def get_the_completion_list(self, what, mode):
return self.autocomplete.fetch_completions(what, mode)
def stackviewer(self, flist_oid=None):
if self.user_exc_info:
_, exc, tb = self.user_exc_info
else:
return None
flist = None
if flist_oid is not None:
flist = self.rpchandler.get_remote_proxy(flist_oid)
while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
tb = tb.tb_next
exc.__traceback__ = tb
item = stackviewer.StackTreeItem(exc, flist)
return debugobj_r.remote_object_tree_item(item)
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_run', verbosity=2)
capture_warnings(False)