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