codekingpro/portable-devtools
114k
1"""runpy.py - locating and running Python code using the module namespace2 3Provides support for locating and running Python scripts using the Python4module namespace instead of the native filesystem.5 6This allows Python code to play nicely with non-filesystem based PEP 3027importers 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 13import sys14import importlib.machinery # importlib first so we can test #15386 via -m15import importlib.util16import io17import os18 19__all__ = [20 "run_module", "run_path",21]22 23# avoid 'import types' just for ModuleType24ModuleType = type(sys)25 26class _TempModule(object):27 """Temporarily replace a module in sys.modules with an empty namespace"""28 def __init__(self, mod_name):29 self.mod_name = mod_name30 self.module = ModuleType(mod_name)31 self._saved_module = []32 33 def __enter__(self):34 mod_name = self.mod_name35 try:36 self._saved_module.append(sys.modules[mod_name])37 except KeyError:38 pass39 sys.modules[mod_name] = self.module40 return self41 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 49class _ModifiedArgv0(object):50 def __init__(self, value):51 self.value = value52 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.value59 60 def __exit__(self, *args):61 self.value = self._sentinel62 sys.argv[0] = self._saved_value63 64# TODO: Replace these helpers with importlib._bootstrap_external functions.65def _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 = None73 fname = script_name74 cached = None75 else:76 loader = mod_spec.loader77 fname = mod_spec.origin78 cached = mod_spec.cached79 if pkg_name is None:80 pkg_name = mod_spec.parent81 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_globals90 91def _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.origin96 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 they101 # may be cleared when the temporary module goes away102 return mod_globals.copy()103 104# Helper to get the full name, spec and code for a module105def _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 errors111 try:112 __import__(pkg_name)113 except ImportError as e:114 # If the parent or higher ancestor package is missing, let the115 # error be raised by find_spec() below and then be caught. But do116 # not allow other errors to be caught.117 if e.name is None or (e.name != pkg_name and118 not pkg_name.startswith(e.name + ".")):119 raise120 # Warn if the module has already been imported under its normal name121 existing = sys.modules.get(mod_name)122 if existing is not None and not hasattr(existing, "__path__"):123 from warnings import warn124 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 and134 # importlib, where the latter raises other errors for cases where135 # pkgutil previously raised ImportError136 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 ex141 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 irrelevant152 raise error(("%s; %r is a package and cannot " +153 "be directly executed") %(e, mod_name))154 loader = spec.loader155 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 e162 if code is None:163 raise error("No code object available for %s" % mod_name)164 return mod_name, spec, code165 166class _Error(Exception):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 its171# creation when run_module() no longer met the needs of172# mainmodule.c, but couldn't be changed because it was public)173def _run_module_as_main(mod_name, alter_argv=True):174 """Runs the designated module in the __main__ namespace175 176 Note that the executed module will have full access to the177 __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 switch189 mod_name, mod_spec, code = _get_module_details(mod_name, _Error)190 else: # i.e. directory or zipfile execution191 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.origin198 return _run_code(code, main_globals, None,199 "__main__", mod_spec)200 201def 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’s209 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 the213 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 of216 __file__ and sys.modules[__name__] is updated with a temporary217 module object for the module being executed. Both are218 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_name225 if alter_sys:226 return _run_module_code(code, init_globals, run_name, mod_spec)227 else:228 # Leave the sys module alone229 return _run_code(code, {}, init_globals, run_name, mod_spec)230 231def _get_main_module_details(error=ImportError):232 # Helper that gives a nicer error message when attempting to233 # execute a zipfile or directory by invoking __main__.py234 # Also moves the standard __main__ out of the way so that the235 # preexisting __loader__ entry doesn't cause issues236 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 exc245 raise246 finally:247 sys.modules[main_name] = saved_main248 249 250def _get_code_from_file(fname):251 # Check for a compiled file first252 from pkgutil import read_code253 code_path = os.path.abspath(fname)254 with io.open_code(code_path) as f:255 code = read_code(f)256 if code is None:257 # That didn't work, so try it as normal source code258 with io.open_code(code_path) as f:259 code = compile(f.read(), fname, 'exec')260 return code261 262def 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’s270 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_importer281 importer = get_importer(path_name)282 path_name = os.fsdecode(path_name)283 if isinstance(importer, type(None)):284 # Not a valid sys.path entry, so run the code directly285 # execfile() doesn't help as we want to allow compiled files286 code = _get_code_from_file(path_name)287 return _run_module_code(code, init_globals, run_name,288 pkg_name=pkg_name, script_name=path_name)289 else:290 # Finder is defined for path, so add it to291 # the start of sys.path292 sys.path.insert(0, path_name)293 try:294 # Here's where things are a little different from the run_module295 # case. There, we only had to replace the module in sys while the296 # code was running and doing so was somewhat optional. Here, we297 # have no choice and we have to remove it even while we read the298 # code. If we don't do this, a __loader__ attribute in the299 # existing __main__ module may prevent location of the new module.300 mod_name, mod_spec, code = _get_main_module_details()301 with _TempModule(run_name) as temp_module, \302 _ModifiedArgv0(path_name):303 mod_globals = temp_module.module.__dict__304 return _run_code(code, mod_globals, init_globals,305 run_name, mod_spec, pkg_name).copy()306 finally:307 try:308 sys.path.remove(path_name)309 except ValueError:310 pass311 312 313if __name__ == "__main__":314 # Run the module specified as the next command line argument315 if len(sys.argv) < 2:316 print("No module specified for execution", file=sys.stderr)317 else:318 del sys.argv[0] # Make the requested module sys.argv[0]319 _run_module_as_main(sys.argv[0])320 