"""runpy.py - locating and running Python code using the module namespace Provides support for locating and running Python scripts using the Python module namespace instead of the native filesystem. Also provides support to make it easier to execute other code without affecting the current function namespace. This allows Python code to play nicely with non-filesystem based PEP 302 importers when locating support scripts as well as when importing modules. """ # Written by Nick Coghlan # to implement PEP 338 (Executing Modules as Scripts) import sys import imp __all__ = [ "run_module", ] try: _get_loader = imp.get_loader except AttributeError: # get_loader() is not provided by the imp module, so emulate it # as best we can using the PEP 302 import machinery exposed since # Python 2.3. The emulation isn't perfect, but the differences # in the way names are shadowed shouldn't matter in practice. import os.path import marshal # Handle compiled Python files # This helper is needed in order for the PEP 302 emulation to # correctly handle compiled files def _read_compiled_file(compiled_file): magic = compiled_file.read(4) if magic != imp.get_magic(): return None try: compiled_file.read(4) # Skip timestamp return marshal.load(compiled_file) except Exception: return None class _AbsoluteImporter(object): """PEP 302 importer wrapper for top level import machinery""" def find_module(self, mod_name, path=None): if path is not None: return None try: file, filename, mod_info = imp.find_module(mod_name) except ImportError: return None suffix, mode, mod_type = mod_info if mod_type == imp.PY_SOURCE: loader = _SourceFileLoader(mod_name, file, filename, mod_info) elif mod_type == imp.PY_COMPILED: loader = _CompiledFileLoader(mod_name, file, filename, mod_info) elif mod_type == imp.PKG_DIRECTORY: loader = _PackageDirLoader(mod_name, file, filename, mod_info) elif mod_type == imp.C_EXTENSION: loader = _FileSystemLoader(mod_name, file, filename, mod_info) else: loader = _BasicLoader(mod_name, file, filename, mod_info) return loader class _FileSystemImporter(object): """PEP 302 importer wrapper for filesystem based imports""" def __init__(self, path_item=None): if path_item is not None: if path_item != '' and not os.path.isdir(path_item): raise ImportError("%s is not a directory" % path_item) self.path_dir = path_item else: raise ImportError("Filesystem importer requires " "a directory name") def find_module(self, mod_name, path=None): if path is not None: return None path_dir = self.path_dir if path_dir == '': path_dir = os.getcwd() sub_name = mod_name.rsplit(".", 1)[-1] try: file, filename, mod_info = imp.find_module(sub_name, [path_dir]) except ImportError: return None if not filename.startswith(path_dir): return None suffix, mode, mod_type = mod_info if mod_type == imp.PY_SOURCE: loader = _SourceFileLoader(mod_name, file, filename, mod_info) elif mod_type == imp.PY_COMPILED: loader = _CompiledFileLoader(mod_name, file, filename, mod_info) elif mod_type == imp.PKG_DIRECTORY: loader = _PackageDirLoader(mod_name, file, filename, mod_info) elif mod_type == imp.C_EXTENSION: loader = _FileSystemLoader(mod_name, file, filename, mod_info) else: loader = _BasicLoader(mod_name, file, filename, mod_info) return loader class _BasicLoader(object): """PEP 302 loader wrapper for top level import machinery""" def __init__(self, mod_name, file, filename, mod_info): self.mod_name = mod_name self.file = file self.filename = filename self.mod_info = mod_info def _fix_name(self, mod_name): if mod_name is None: mod_name = self.mod_name elif mod_name != self.mod_name: raise ImportError("Loader for module %s cannot handle " "module %s" % (self.mod_name, mod_name)) return mod_name def load_module(self, mod_name=None): mod_name = self._fix_name(mod_name) mod = imp.load_module(mod_name, self.file, self.filename, self.mod_info) mod.__loader__ = self # for introspection return mod def get_code(self, mod_name=None): return None def get_source(self, mod_name=None): return None def is_package(self, mod_name=None): return False def close(self): if self.file: self.file.close() def __del__(self): self.close() class _FileSystemLoader(_BasicLoader): """PEP 302 loader wrapper for filesystem based imports""" def get_code(self, mod_name=None): mod_name = self._fix_name(mod_name) return self._get_code(mod_name) def get_data(self, pathname): return open(pathname, "rb").read() def get_filename(self, mod_name=None): mod_name = self._fix_name(mod_name) return self._get_filename(mod_name) def get_source(self, mod_name=None): mod_name = self._fix_name(mod_name) return self._get_source(mod_name) def is_package(self, mod_name=None): mod_name = self._fix_name(mod_name) return self._is_package(mod_name) def _get_code(self, mod_name): return None def _get_filename(self, mod_name): return self.filename def _get_source(self, mod_name): return None def _is_package(self, mod_name): return False class _PackageDirLoader(_FileSystemLoader): """PEP 302 loader wrapper for PKG_DIRECTORY directories""" def _is_package(self, mod_name): return True class _SourceFileLoader(_FileSystemLoader): """PEP 302 loader wrapper for PY_SOURCE modules""" def _get_code(self, mod_name): return compile(self._get_source(mod_name), self.filename, 'exec') def _get_source(self, mod_name): f = self.file f.seek(0) return f.read() class _CompiledFileLoader(_FileSystemLoader): """PEP 302 loader wrapper for PY_COMPILED modules""" def _get_code(self, mod_name): f = self.file f.seek(0) return _read_compiled_file(f) def _get_importer(path_item): """Retrieve a PEP 302 importer for the given path item The returned importer is cached in sys.path_importer_cache if it was newly created by a path hook. If there is no importer, a wrapper around the basic import machinery is returned. This wrapper is never inserted into the importer cache (None is inserted instead). The cache (or part of it) can be cleared manually if a rescan of sys.path_hooks is necessary. """ try: importer = sys.path_importer_cache[path_item] except KeyError: for path_hook in sys.path_hooks: try: importer = path_hook(path_item) break except ImportError: pass else: importer = None sys.path_importer_cache[path_item] = importer if importer is None: try: importer = _FileSystemImporter(path_item) except ImportError: pass return importer def _get_path_loader(mod_name, path=None): """Retrieve a PEP 302 loader using a path importer""" if path is None: path = sys.path absolute_loader = _AbsoluteImporter().find_module(mod_name) if isinstance(absolute_loader, _FileSystemLoader): # Found in filesystem, so scan path hooks # before accepting this one as the right one loader = None else: # Not found in filesystem, so use top-level loader loader = absolute_loader else: loader = absolute_loader = None if loader is None: for path_item in path: importer = _get_importer(path_item) if importer is not None: loader = importer.find_module(mod_name) if loader is not None: # Found a loader for our module break else: # No path hook found, so accept the top level loader loader = absolute_loader return loader def _get_package(pkg_name): """Retrieve a named package""" pkg = __import__(pkg_name) sub_pkg_names = pkg_name.split(".") for sub_pkg in sub_pkg_names[1:]: pkg = getattr(pkg, sub_pkg) return pkg def _get_loader(mod_name, path=None): """Retrieve a PEP 302 loader for the given module or package If the module or package is accessible via the normal import mechanism, a wrapper around the relevant part of that machinery is returned. Non PEP 302 mechanisms (e.g. the Windows registry) used by the standard import machinery to find files in alternative locations are partially supported, but are searched AFTER sys.path. Normally, these locations are searched BEFORE sys.path, preventing sys.path entries from shadowing them. For this to cause a visible difference in behaviour, there must be a module or package name that is accessible via both sys.path and one of the non PEP 302 file system mechanisms. In this case, the emulation will find the former version, while the builtin import mechanism will find the latter. Items of the following types can be affected by this discrepancy: imp.C_EXTENSION imp.PY_SOURCE imp.PY_COMPILED imp.PKG_DIRECTORY """ try: loader = sys.modules[mod_name].__loader__ except (KeyError, AttributeError): loader = None if loader is None: imp.acquire_lock() try: # Module not in sys.modules, or uses an unhooked loader parts = mod_name.rsplit(".", 1) if len(parts) == 2: # Sub package, so use parent package's path pkg_name, sub_name = parts if pkg_name and pkg_name[0] != '.': if path is not None: raise ImportError("Path argument must be None " "for a dotted module name") pkg = _get_package(pkg_name) try: path = pkg.__path__ except AttributeError: raise ImportError(pkg_name + " is not a package") else: raise ImportError("Relative import syntax is not " "supported by _get_loader()") else: # Top level module, so stick with default path sub_name = mod_name for importer in sys.meta_path: loader = importer.find_module(mod_name, path) if loader is not None: # Found a metahook to handle the module break else: # Handling via the standard path mechanism loader = _get_path_loader(mod_name, path) finally: imp.release_lock() return loader # This helper is needed due to a missing component in the PEP 302 # loader protocol (specifically, "get_filename" is non-standard) def _get_filename(loader, mod_name): try: get_filename = loader.get_filename except AttributeError: return None else: return get_filename(mod_name) # ------------------------------------------------------------ # Done with the import machinery emulation, on with the code! def _run_code(code, run_globals, init_globals, mod_name, mod_fname, mod_loader): """Helper for _run_module_code""" if init_globals is not None: run_globals.update(init_globals) run_globals.update(__name__ = mod_name, __file__ = mod_fname, __loader__ = mod_loader) exec code in run_globals return run_globals def _run_module_code(code, init_globals=None, mod_name=None, mod_fname=None, mod_loader=None, alter_sys=False): """Helper for run_module""" # Set up the top level namespace dictionary if alter_sys: # Modify sys.argv[0] and sys.module[mod_name] temp_module = imp.new_module(mod_name) mod_globals = temp_module.__dict__ saved_argv0 = sys.argv[0] restore_module = mod_name in sys.modules if restore_module: saved_module = sys.modules[mod_name] imp.acquire_lock() try: sys.argv[0] = mod_fname sys.modules[mod_name] = temp_module try: _run_code(code, mod_globals, init_globals, mod_name, mod_fname, mod_loader) finally: sys.argv[0] = saved_argv0 if restore_module: sys.modules[mod_name] = saved_module else: del sys.modules[mod_name] finally: imp.release_lock() # Copy the globals of the temporary module, as they # may be cleared when the temporary module goes away return mod_globals.copy() else: # Leave the sys module alone return _run_code(code, {}, init_globals, mod_name, mod_fname, mod_loader) def run_module(mod_name, init_globals=None, run_name=None, alter_sys=False): """Execute a module's code without importing it Returns the resulting top level namespace dictionary """ loader = _get_loader(mod_name) if loader is None: raise ImportError("No module named " + mod_name) code = loader.get_code(mod_name) if code is None: raise ImportError("No code object available for " + mod_name) filename = _get_filename(loader, mod_name) if run_name is None: run_name = mod_name return _run_module_code(code, init_globals, run_name, filename, loader, alter_sys) if __name__ == "__main__": # Run the module specified as the next command line argument if len(sys.argv) < 2: print >> sys.stderr, "No module specified for execution" else: del sys.argv[0] # Make the requested module sys.argv[0] run_module(sys.argv[0], run_name="__main__", alter_sys=True)