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