python (3.12.0)

(root)/
lib/
python3.12/
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      sys.last_exc = val
     244      seen = set()
     245  
     246      def print_exc(typ, exc, tb):
     247          seen.add(id(exc))
     248          context = exc.__context__
     249          cause = exc.__cause__
     250          if cause is not None and id(cause) not in seen:
     251              print_exc(type(cause), cause, cause.__traceback__)
     252              print("\nThe above exception was the direct cause "
     253                    "of the following exception:\n", file=efile)
     254          elif (context is not None and
     255                not exc.__suppress_context__ and
     256                id(context) not in seen):
     257              print_exc(type(context), context, context.__traceback__)
     258              print("\nDuring handling of the above exception, "
     259                    "another exception occurred:\n", file=efile)
     260          if tb:
     261              tbe = traceback.extract_tb(tb)
     262              print('Traceback (most recent call last):', file=efile)
     263              exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
     264                         "debugger_r.py", "bdb.py")
     265              cleanup_traceback(tbe, exclude)
     266              traceback.print_list(tbe, file=efile)
     267          lines = get_message_lines(typ, exc, tb)
     268          for line in lines:
     269              print(line, end='', file=efile)
     270  
     271      print_exc(typ, val, tb)
     272  
     273  def cleanup_traceback(tb, exclude):
     274      "Remove excluded traces from beginning/end of tb; get cached lines"
     275      orig_tb = tb[:]
     276      while tb:
     277          for rpcfile in exclude:
     278              if tb[0][0].count(rpcfile):
     279                  break    # found an exclude, break for: and delete tb[0]
     280          else:
     281              break        # no excludes, have left RPC code, break while:
     282          del tb[0]
     283      while tb:
     284          for rpcfile in exclude:
     285              if tb[-1][0].count(rpcfile):
     286                  break
     287          else:
     288              break
     289          del tb[-1]
     290      if len(tb) == 0:
     291          # exception was in IDLE internals, don't prune!
     292          tb[:] = orig_tb[:]
     293          print("** IDLE Internal Exception: ", file=sys.stderr)
     294      rpchandler = rpc.objecttable['exec'].rpchandler
     295      for i in range(len(tb)):
     296          fn, ln, nm, line = tb[i]
     297          if nm == '?':
     298              nm = "-toplevel-"
     299          if not line and fn.startswith("<pyshell#"):
     300              line = rpchandler.remotecall('linecache', 'getline',
     301                                                (fn, ln), {})
     302          tb[i] = fn, ln, nm, line
     303  
     304  def flush_stdout():
     305      """XXX How to do this now?"""
     306  
     307  def exit():
     308      """Exit subprocess, possibly after first clearing exit functions.
     309  
     310      If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any
     311      functions registered with atexit will be removed before exiting.
     312      (VPython support)
     313  
     314      """
     315      if no_exitfunc:
     316          import atexit
     317          atexit._clear()
     318      capture_warnings(False)
     319      sys.exit(0)
     320  
     321  
     322  def fix_scaling(root):
     323      """Scale fonts on HiDPI displays."""
     324      import tkinter.font
     325      scaling = float(root.tk.call('tk', 'scaling'))
     326      if scaling > 1.4:
     327          for name in tkinter.font.names(root):
     328              font = tkinter.font.Font(root=root, name=name, exists=True)
     329              size = int(font['size'])
     330              if size < 0:
     331                  font['size'] = round(-0.75*size)
     332  
     333  
     334  def fixdoc(fun, text):
     335      tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else ''
     336      fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text))
     337  
     338  RECURSIONLIMIT_DELTA = 30
     339  
     340  def install_recursionlimit_wrappers():
     341      """Install wrappers to always add 30 to the recursion limit."""
     342      # see: bpo-26806
     343  
     344      @functools.wraps(sys.setrecursionlimit)
     345      def setrecursionlimit(*args, **kwargs):
     346          # mimic the original sys.setrecursionlimit()'s input handling
     347          if kwargs:
     348              raise TypeError(
     349                  "setrecursionlimit() takes no keyword arguments")
     350          try:
     351              limit, = args
     352          except ValueError:
     353              raise TypeError(f"setrecursionlimit() takes exactly one "
     354                              f"argument ({len(args)} given)")
     355          if not limit > 0:
     356              raise ValueError(
     357                  "recursion limit must be greater or equal than 1")
     358  
     359          return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA)
     360  
     361      fixdoc(setrecursionlimit, f"""\
     362              This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible
     363              uninterruptible loops.""")
     364  
     365      @functools.wraps(sys.getrecursionlimit)
     366      def getrecursionlimit():
     367          return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA
     368  
     369      fixdoc(getrecursionlimit, f"""\
     370              This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate
     371              for the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.""")
     372  
     373      # add the delta to the default recursion limit, to compensate
     374      sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA)
     375  
     376      sys.setrecursionlimit = setrecursionlimit
     377      sys.getrecursionlimit = getrecursionlimit
     378  
     379  
     380  def uninstall_recursionlimit_wrappers():
     381      """Uninstall the recursion limit wrappers from the sys module.
     382  
     383      IDLE only uses this for tests. Users can import run and call
     384      this to remove the wrapping.
     385      """
     386      if (
     387              getattr(sys.setrecursionlimit, '__wrapped__', None) and
     388              getattr(sys.getrecursionlimit, '__wrapped__', None)
     389      ):
     390          sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__
     391          sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__
     392          sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA)
     393  
     394  
     395  class ESC[4;38;5;81mMyRPCServer(ESC[4;38;5;149mrpcESC[4;38;5;149m.ESC[4;38;5;149mRPCServer):
     396  
     397      def handle_error(self, request, client_address):
     398          """Override RPCServer method for IDLE
     399  
     400          Interrupt the MainThread and exit server if link is dropped.
     401  
     402          """
     403          global quitting
     404          try:
     405              raise
     406          except SystemExit:
     407              raise
     408          except EOFError:
     409              global exit_now
     410              exit_now = True
     411              thread.interrupt_main()
     412          except:
     413              erf = sys.__stderr__
     414              print(textwrap.dedent(f"""
     415              {'-'*40}
     416              Unhandled exception in user code execution server!'
     417              Thread: {threading.current_thread().name}
     418              IDLE Client Address: {client_address}
     419              Request: {request!r}
     420              """), file=erf)
     421              traceback.print_exc(limit=-20, file=erf)
     422              print(textwrap.dedent(f"""
     423              *** Unrecoverable, server exiting!
     424  
     425              Users should never see this message; it is likely transient.
     426              If this recurs, report this with a copy of the message
     427              and an explanation of how to make it repeat.
     428              {'-'*40}"""), file=erf)
     429              quitting = True
     430              thread.interrupt_main()
     431  
     432  
     433  # Pseudofiles for shell-remote communication (also used in pyshell)
     434  
     435  class ESC[4;38;5;81mStdioFile(ESC[4;38;5;149mioESC[4;38;5;149m.ESC[4;38;5;149mTextIOBase):
     436  
     437      def __init__(self, shell, tags, encoding='utf-8', errors='strict'):
     438          self.shell = shell
     439          self.tags = tags
     440          self._encoding = encoding
     441          self._errors = errors
     442  
     443      @property
     444      def encoding(self):
     445          return self._encoding
     446  
     447      @property
     448      def errors(self):
     449          return self._errors
     450  
     451      @property
     452      def name(self):
     453          return '<%s>' % self.tags
     454  
     455      def isatty(self):
     456          return True
     457  
     458  
     459  class ESC[4;38;5;81mStdOutputFile(ESC[4;38;5;149mStdioFile):
     460  
     461      def writable(self):
     462          return True
     463  
     464      def write(self, s):
     465          if self.closed:
     466              raise ValueError("write to closed file")
     467          s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors)
     468          return self.shell.write(s, self.tags)
     469  
     470  
     471  class ESC[4;38;5;81mStdInputFile(ESC[4;38;5;149mStdioFile):
     472      _line_buffer = ''
     473  
     474      def readable(self):
     475          return True
     476  
     477      def read(self, size=-1):
     478          if self.closed:
     479              raise ValueError("read from closed file")
     480          if size is None:
     481              size = -1
     482          elif not isinstance(size, int):
     483              raise TypeError('must be int, not ' + type(size).__name__)
     484          result = self._line_buffer
     485          self._line_buffer = ''
     486          if size < 0:
     487              while line := self.shell.readline():
     488                  result += line
     489          else:
     490              while len(result) < size:
     491                  line = self.shell.readline()
     492                  if not line: break
     493                  result += line
     494              self._line_buffer = result[size:]
     495              result = result[:size]
     496          return result
     497  
     498      def readline(self, size=-1):
     499          if self.closed:
     500              raise ValueError("read from closed file")
     501          if size is None:
     502              size = -1
     503          elif not isinstance(size, int):
     504              raise TypeError('must be int, not ' + type(size).__name__)
     505          line = self._line_buffer or self.shell.readline()
     506          if size < 0:
     507              size = len(line)
     508          eol = line.find('\n', 0, size)
     509          if eol >= 0:
     510              size = eol + 1
     511          self._line_buffer = line[size:]
     512          return line[:size]
     513  
     514      def close(self):
     515          self.shell.close()
     516  
     517  
     518  class ESC[4;38;5;81mMyHandler(ESC[4;38;5;149mrpcESC[4;38;5;149m.ESC[4;38;5;149mRPCHandler):
     519  
     520      def handle(self):
     521          """Override base method"""
     522          executive = Executive(self)
     523          self.register("exec", executive)
     524          self.console = self.get_remote_proxy("console")
     525          sys.stdin = StdInputFile(self.console, "stdin",
     526                                   iomenu.encoding, iomenu.errors)
     527          sys.stdout = StdOutputFile(self.console, "stdout",
     528                                     iomenu.encoding, iomenu.errors)
     529          sys.stderr = StdOutputFile(self.console, "stderr",
     530                                     iomenu.encoding, "backslashreplace")
     531  
     532          sys.displayhook = rpc.displayhook
     533          # page help() text to shell.
     534          import pydoc # import must be done here to capture i/o binding
     535          pydoc.pager = pydoc.plainpager
     536  
     537          # Keep a reference to stdin so that it won't try to exit IDLE if
     538          # sys.stdin gets changed from within IDLE's shell. See issue17838.
     539          self._keep_stdin = sys.stdin
     540  
     541          install_recursionlimit_wrappers()
     542  
     543          self.interp = self.get_remote_proxy("interp")
     544          rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
     545  
     546      def exithook(self):
     547          "override SocketIO method - wait for MainThread to shut us down"
     548          time.sleep(10)
     549  
     550      def EOFhook(self):
     551          "Override SocketIO method - terminate wait on callback and exit thread"
     552          global quitting
     553          quitting = True
     554          thread.interrupt_main()
     555  
     556      def decode_interrupthook(self):
     557          "interrupt awakened thread"
     558          global quitting
     559          quitting = True
     560          thread.interrupt_main()
     561  
     562  
     563  class ESC[4;38;5;81mExecutive:
     564  
     565      def __init__(self, rpchandler):
     566          self.rpchandler = rpchandler
     567          if idlelib.testing is False:
     568              self.locals = __main__.__dict__
     569              self.calltip = calltip.Calltip()
     570              self.autocomplete = autocomplete.AutoComplete()
     571          else:
     572              self.locals = {}
     573  
     574      def runcode(self, code):
     575          global interruptable
     576          try:
     577              self.user_exc_info = None
     578              interruptable = True
     579              try:
     580                  exec(code, self.locals)
     581              finally:
     582                  interruptable = False
     583          except SystemExit as e:
     584              if e.args:  # SystemExit called with an argument.
     585                  ob = e.args[0]
     586                  if not isinstance(ob, (type(None), int)):
     587                      print('SystemExit: ' + str(ob), file=sys.stderr)
     588              # Return to the interactive prompt.
     589          except:
     590              self.user_exc_info = sys.exc_info()  # For testing, hook, viewer.
     591              if quitting:
     592                  exit()
     593              if sys.excepthook is sys.__excepthook__:
     594                  print_exception()
     595              else:
     596                  try:
     597                      sys.excepthook(*self.user_exc_info)
     598                  except:
     599                      self.user_exc_info = sys.exc_info()  # For testing.
     600                      print_exception()
     601              jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>")
     602              if jit:
     603                  self.rpchandler.interp.open_remote_stack_viewer()
     604          else:
     605              flush_stdout()
     606  
     607      def interrupt_the_server(self):
     608          if interruptable:
     609              thread.interrupt_main()
     610  
     611      def start_the_debugger(self, gui_adap_oid):
     612          return debugger_r.start_debugger(self.rpchandler, gui_adap_oid)
     613  
     614      def stop_the_debugger(self, idb_adap_oid):
     615          "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
     616          self.rpchandler.unregister(idb_adap_oid)
     617  
     618      def get_the_calltip(self, name):
     619          return self.calltip.fetch_tip(name)
     620  
     621      def get_the_completion_list(self, what, mode):
     622          return self.autocomplete.fetch_completions(what, mode)
     623  
     624      def stackviewer(self, flist_oid=None):
     625          if self.user_exc_info:
     626              _, exc, tb = self.user_exc_info
     627          else:
     628              return None
     629          flist = None
     630          if flist_oid is not None:
     631              flist = self.rpchandler.get_remote_proxy(flist_oid)
     632          while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
     633              tb = tb.tb_next
     634          exc.__traceback__ = tb
     635          item = stackviewer.StackTreeItem(exc, flist)
     636          return debugobj_r.remote_object_tree_item(item)
     637  
     638  
     639  if __name__ == '__main__':
     640      from unittest import main
     641      main('idlelib.idle_test.test_run', verbosity=2)
     642  
     643  capture_warnings(False)  # Make sure turned off; see bpo-18081.