1"""runpy.py - locating and running Python code using the module namespace 2 3Provides support for locating and running Python scripts using the Python 4module namespace instead of the native filesystem. 5 6This allows Python code to play nicely with non-filesystem based PEP 302 7importers 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 sys 14import importlib.machinery # importlib first so we can test #15386 via -m 15import importlib.util 16import io 17import os 18 19__all__ = [ 20 "run_module", "run_path", 21] 22 23# avoid 'import types' just for ModuleType 24ModuleType = 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_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 49class _ModifiedArgv0(object): 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. 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 = 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 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.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 105def _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 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 its 171# creation when run_module() no longer met the needs of 172# 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__ 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 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’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 231def _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 250def _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 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’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 # Trying to avoid importing imp so as to not consume the deprecation warning. 283 is_NullImporter = False 284 if type(importer).__module__ == 'imp': 285 if type(importer).__name__ == 'NullImporter': 286 is_NullImporter = True 287 if isinstance(importer, type(None)) or is_NullImporter: 288 # Not a valid sys.path entry, so run the code directly 289 # execfile() doesn't help as we want to allow compiled files 290 code, fname = _get_code_from_file(run_name, path_name) 291 return _run_module_code(code, init_globals, run_name, 292 pkg_name=pkg_name, script_name=fname) 293 else: 294 # Finder is defined for path, so add it to 295 # the start of sys.path 296 sys.path.insert(0, path_name) 297 try: 298 # Here's where things are a little different from the run_module 299 # case. There, we only had to replace the module in sys while the 300 # code was running and doing so was somewhat optional. Here, we 301 # have no choice and we have to remove it even while we read the 302 # code. If we don't do this, a __loader__ attribute in the 303 # existing __main__ module may prevent location of the new module. 304 mod_name, mod_spec, code = _get_main_module_details() 305 with _TempModule(run_name) as temp_module, \ 306 _ModifiedArgv0(path_name): 307 mod_globals = temp_module.module.__dict__ 308 return _run_code(code, mod_globals, init_globals, 309 run_name, mod_spec, pkg_name).copy() 310 finally: 311 try: 312 sys.path.remove(path_name) 313 except ValueError: 314 pass 315 316 317if __name__ == "__main__": 318 # Run the module specified as the next command line argument 319 if len(sys.argv) < 2: 320 print("No module specified for execution", file=sys.stderr) 321 else: 322 del sys.argv[0] # Make the requested module sys.argv[0] 323 _run_module_as_main(sys.argv[0]) 324