| """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. |
| |
| 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 <ncoghlan at gmail.com> |
| # 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] |
| 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] |
| # 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) |