(root)/
Python-3.12.0/
Lib/
runpy.py
       1  """runpy.py - locating and running Python code using the module namespace
       2  
       3  Provides support for locating and running Python scripts using the Python
       4  module namespace instead of the native filesystem.
       5  
       6  This allows Python code to play nicely with non-filesystem based PEP 302
       7  importers when locating support scripts as well as when importing modules.
       8  """
       9  # Written by Nick Coghlan <ncoghlan at gmail.com>
      10  #    to implement PEP 338 (Executing Modules as Scripts)
      11  
      12  
      13  import sys
      14  import importlib.machinery # importlib first so we can test #15386 via -m
      15  import importlib.util
      16  import io
      17  import os
      18  
      19  __all__ = [
      20      "run_module", "run_path",
      21  ]
      22  
      23  # avoid 'import types' just for ModuleType
      24  ModuleType = type(sys)
      25  
      26  class ESC[4;38;5;81m_TempModule(ESC[4;38;5;149mobject):
      27      """Temporarily replace a module in sys.modules with an empty namespace"""
      28      def __init__(self, mod_name):
      29          self.mod_name = mod_name
      30          self.module = ModuleType(mod_name)
      31          self._saved_module = []
      32  
      33      def __enter__(self):
      34          mod_name = self.mod_name
      35          try:
      36              self._saved_module.append(sys.modules[mod_name])
      37          except KeyError:
      38              pass
      39          sys.modules[mod_name] = self.module
      40          return self
      41  
      42      def __exit__(self, *args):
      43          if self._saved_module:
      44              sys.modules[self.mod_name] = self._saved_module[0]
      45          else:
      46              del sys.modules[self.mod_name]
      47          self._saved_module = []
      48  
      49  class ESC[4;38;5;81m_ModifiedArgv0(ESC[4;38;5;149mobject):
      50      def __init__(self, value):
      51          self.value = value
      52          self._saved_value = self._sentinel = object()
      53  
      54      def __enter__(self):
      55          if self._saved_value is not self._sentinel:
      56              raise RuntimeError("Already preserving saved value")
      57          self._saved_value = sys.argv[0]
      58          sys.argv[0] = self.value
      59  
      60      def __exit__(self, *args):
      61          self.value = self._sentinel
      62          sys.argv[0] = self._saved_value
      63  
      64  # TODO: Replace these helpers with importlib._bootstrap_external functions.
      65  def _run_code(code, run_globals, init_globals=None,
      66                mod_name=None, mod_spec=None,
      67                pkg_name=None, script_name=None):
      68      """Helper to run code in nominated namespace"""
      69      if init_globals is not None:
      70          run_globals.update(init_globals)
      71      if mod_spec is None:
      72          loader = None
      73          fname = script_name
      74          cached = None
      75      else:
      76          loader = mod_spec.loader
      77          fname = mod_spec.origin
      78          cached = mod_spec.cached
      79          if pkg_name is None:
      80              pkg_name = mod_spec.parent
      81      run_globals.update(__name__ = mod_name,
      82                         __file__ = fname,
      83                         __cached__ = cached,
      84                         __doc__ = None,
      85                         __loader__ = loader,
      86                         __package__ = pkg_name,
      87                         __spec__ = mod_spec)
      88      exec(code, run_globals)
      89      return run_globals
      90  
      91  def _run_module_code(code, init_globals=None,
      92                      mod_name=None, mod_spec=None,
      93                      pkg_name=None, script_name=None):
      94      """Helper to run code in new namespace with sys modified"""
      95      fname = script_name if mod_spec is None else mod_spec.origin
      96      with _TempModule(mod_name) as temp_module, _ModifiedArgv0(fname):
      97          mod_globals = temp_module.module.__dict__
      98          _run_code(code, mod_globals, init_globals,
      99                    mod_name, mod_spec, pkg_name, script_name)
     100      # Copy the globals of the temporary module, as they
     101      # may be cleared when the temporary module goes away
     102      return mod_globals.copy()
     103  
     104  # Helper to get the full name, spec and code for a module
     105  def _get_module_details(mod_name, error=ImportError):
     106      if mod_name.startswith("."):
     107          raise error("Relative module names not supported")
     108      pkg_name, _, _ = mod_name.rpartition(".")
     109      if pkg_name:
     110          # Try importing the parent to avoid catching initialization errors
     111          try:
     112              __import__(pkg_name)
     113          except ImportError as e:
     114              # If the parent or higher ancestor package is missing, let the
     115              # error be raised by find_spec() below and then be caught. But do
     116              # not allow other errors to be caught.
     117              if e.name is None or (e.name != pkg_name and
     118                      not pkg_name.startswith(e.name + ".")):
     119                  raise
     120          # Warn if the module has already been imported under its normal name
     121          existing = sys.modules.get(mod_name)
     122          if existing is not None and not hasattr(existing, "__path__"):
     123              from warnings import warn
     124              msg = "{mod_name!r} found in sys.modules after import of " \
     125                  "package {pkg_name!r}, but prior to execution of " \
     126                  "{mod_name!r}; this may result in unpredictable " \
     127                  "behaviour".format(mod_name=mod_name, pkg_name=pkg_name)
     128              warn(RuntimeWarning(msg))
     129  
     130      try:
     131          spec = importlib.util.find_spec(mod_name)
     132      except (ImportError, AttributeError, TypeError, ValueError) as ex:
     133          # This hack fixes an impedance mismatch between pkgutil and
     134          # importlib, where the latter raises other errors for cases where
     135          # pkgutil previously raised ImportError
     136          msg = "Error while finding module specification for {!r} ({}: {})"
     137          if mod_name.endswith(".py"):
     138              msg += (f". Try using '{mod_name[:-3]}' instead of "
     139                      f"'{mod_name}' as the module name.")
     140          raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
     141      if spec is None:
     142          raise error("No module named %s" % mod_name)
     143      if spec.submodule_search_locations is not None:
     144          if mod_name == "__main__" or mod_name.endswith(".__main__"):
     145              raise error("Cannot use package as __main__ module")
     146          try:
     147              pkg_main_name = mod_name + ".__main__"
     148              return _get_module_details(pkg_main_name, error)
     149          except error as e:
     150              if mod_name not in sys.modules:
     151                  raise  # No module loaded; being a package is irrelevant
     152              raise error(("%s; %r is a package and cannot " +
     153                                 "be directly executed") %(e, mod_name))
     154      loader = spec.loader
     155      if loader is None:
     156          raise error("%r is a namespace package and cannot be executed"
     157                                                                   % mod_name)
     158      try:
     159          code = loader.get_code(mod_name)
     160      except ImportError as e:
     161          raise error(format(e)) from e
     162      if code is None:
     163          raise error("No code object available for %s" % mod_name)
     164      return mod_name, spec, code
     165  
     166  class ESC[4;38;5;81m_Error(ESC[4;38;5;149mException):
     167      """Error that _run_module_as_main() should report without a traceback"""
     168  
     169  # XXX ncoghlan: Should this be documented and made public?
     170  # (Current thoughts: don't repeat the mistake that lead to its
     171  # creation when run_module() no longer met the needs of
     172  # mainmodule.c, but couldn't be changed because it was public)
     173  def _run_module_as_main(mod_name, alter_argv=True):
     174      """Runs the designated module in the __main__ namespace
     175  
     176         Note that the executed module will have full access to the
     177         __main__ namespace. If this is not desirable, the run_module()
     178         function should be used to run the module code in a fresh namespace.
     179  
     180         At the very least, these variables in __main__ will be overwritten:
     181             __name__
     182             __file__
     183             __cached__
     184             __loader__
     185             __package__
     186      """
     187      try:
     188          if alter_argv or mod_name != "__main__": # i.e. -m switch
     189              mod_name, mod_spec, code = _get_module_details(mod_name, _Error)
     190          else:          # i.e. directory or zipfile execution
     191              mod_name, mod_spec, code = _get_main_module_details(_Error)
     192      except _Error as exc:
     193          msg = "%s: %s" % (sys.executable, exc)
     194          sys.exit(msg)
     195      main_globals = sys.modules["__main__"].__dict__
     196      if alter_argv:
     197          sys.argv[0] = mod_spec.origin
     198      return _run_code(code, main_globals, None,
     199                       "__main__", mod_spec)
     200  
     201  def run_module(mod_name, init_globals=None,
     202                 run_name=None, alter_sys=False):
     203      """Execute a module's code without importing it.
     204  
     205         mod_name -- an absolute module name or package name.
     206  
     207         Optional arguments:
     208         init_globals -- dictionary used to pre-populate the module’s
     209         globals dictionary before the code is executed.
     210  
     211         run_name -- if not None, this will be used for setting __name__;
     212         otherwise, __name__ will be set to mod_name + '__main__' if the
     213         named module is a package and to just mod_name otherwise.
     214  
     215         alter_sys -- if True, sys.argv[0] is updated with the value of
     216         __file__ and sys.modules[__name__] is updated with a temporary
     217         module object for the module being executed. Both are
     218         restored to their original values before the function returns.
     219  
     220         Returns the resulting module globals dictionary.
     221      """
     222      mod_name, mod_spec, code = _get_module_details(mod_name)
     223      if run_name is None:
     224          run_name = mod_name
     225      if alter_sys:
     226          return _run_module_code(code, init_globals, run_name, mod_spec)
     227      else:
     228          # Leave the sys module alone
     229          return _run_code(code, {}, init_globals, run_name, mod_spec)
     230  
     231  def _get_main_module_details(error=ImportError):
     232      # Helper that gives a nicer error message when attempting to
     233      # execute a zipfile or directory by invoking __main__.py
     234      # Also moves the standard __main__ out of the way so that the
     235      # preexisting __loader__ entry doesn't cause issues
     236      main_name = "__main__"
     237      saved_main = sys.modules[main_name]
     238      del sys.modules[main_name]
     239      try:
     240          return _get_module_details(main_name)
     241      except ImportError as exc:
     242          if main_name in str(exc):
     243              raise error("can't find %r module in %r" %
     244                                (main_name, sys.path[0])) from exc
     245          raise
     246      finally:
     247          sys.modules[main_name] = saved_main
     248  
     249  
     250  def _get_code_from_file(run_name, fname):
     251      # Check for a compiled file first
     252      from pkgutil import read_code
     253      decoded_path = os.path.abspath(os.fsdecode(fname))
     254      with io.open_code(decoded_path) as f:
     255          code = read_code(f)
     256      if code is None:
     257          # That didn't work, so try it as normal source code
     258          with io.open_code(decoded_path) as f:
     259              code = compile(f.read(), fname, 'exec')
     260      return code, fname
     261  
     262  def run_path(path_name, init_globals=None, run_name=None):
     263      """Execute code located at the specified filesystem location.
     264  
     265         path_name -- filesystem location of a Python script, zipfile,
     266         or directory containing a top level __main__.py script.
     267  
     268         Optional arguments:
     269         init_globals -- dictionary used to pre-populate the module’s
     270         globals dictionary before the code is executed.
     271  
     272         run_name -- if not None, this will be used to set __name__;
     273         otherwise, '<run_path>' will be used for __name__.
     274  
     275         Returns the resulting module globals dictionary.
     276      """
     277      if run_name is None:
     278          run_name = "<run_path>"
     279      pkg_name = run_name.rpartition(".")[0]
     280      from pkgutil import get_importer
     281      importer = get_importer(path_name)
     282      if isinstance(importer, type(None)):
     283          # Not a valid sys.path entry, so run the code directly
     284          # execfile() doesn't help as we want to allow compiled files
     285          code, fname = _get_code_from_file(run_name, path_name)
     286          return _run_module_code(code, init_globals, run_name,
     287                                  pkg_name=pkg_name, script_name=fname)
     288      else:
     289          # Finder is defined for path, so add it to
     290          # the start of sys.path
     291          sys.path.insert(0, path_name)
     292          try:
     293              # Here's where things are a little different from the run_module
     294              # case. There, we only had to replace the module in sys while the
     295              # code was running and doing so was somewhat optional. Here, we
     296              # have no choice and we have to remove it even while we read the
     297              # code. If we don't do this, a __loader__ attribute in the
     298              # existing __main__ module may prevent location of the new module.
     299              mod_name, mod_spec, code = _get_main_module_details()
     300              with _TempModule(run_name) as temp_module, \
     301                   _ModifiedArgv0(path_name):
     302                  mod_globals = temp_module.module.__dict__
     303                  return _run_code(code, mod_globals, init_globals,
     304                                      run_name, mod_spec, pkg_name).copy()
     305          finally:
     306              try:
     307                  sys.path.remove(path_name)
     308              except ValueError:
     309                  pass
     310  
     311  
     312  if __name__ == "__main__":
     313      # Run the module specified as the next command line argument
     314      if len(sys.argv) < 2:
     315          print("No module specified for execution", file=sys.stderr)
     316      else:
     317          del sys.argv[0] # Make the requested module sys.argv[0]
     318          _run_module_as_main(sys.argv[0])