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