| """Import hook support. |
| |
| Consistent use of this module will make it possible to change the |
| different mechanisms involved in loading modules independently. |
| |
| While the built-in module imp exports interfaces to the built-in |
| module searching and loading algorithm, and it is possible to replace |
| the built-in function __import__ in order to change the semantics of |
| the import statement, until now it has been difficult to combine the |
| effect of different __import__ hacks, like loading modules from URLs |
| by rimport.py. |
| |
| This module defines three new concepts: |
| |
| 1) A "file system hooks" class provides an interface to a filesystem. |
| |
| One hooks class is defined (Hooks), which uses the interface provided |
| by standard modules os and os.path. It should be used as the base |
| class for other hooks classes. |
| |
| 2) A "module loader" class provides an interface to search for a |
| module in a search path and to load it. It defines a method which |
| searches for a module in a single directory; by overriding this method |
| one can redefine the details of the search. If the directory is None, |
| built-in and frozen modules are searched instead. |
| |
| Two module loader class are defined, both implementing the search |
| strategy used by the built-in __import__ function: ModuleLoader uses |
| the imp module's find_module interface, while HookableModuleLoader |
| uses a file system hooks class to interact with the file system. Both |
| use the imp module's load_* interfaces to actually load the module. |
| |
| 3) A "module importer" class provides an interface to import a |
| module, as well as interfaces to reload and unload a module. It also |
| provides interfaces to install and uninstall itself instead of the |
| default __import__ and reload (and unload) functions. |
| |
| One module importer class is defined (ModuleImporter), which uses a |
| module loader instance passed in (by default HookableModuleLoader is |
| instantiated). |
| |
| The classes defined here should be used as base classes for extended |
| functionality along those lines. |
| |
| If a module importer class supports dotted names, its import_module() |
| must return a different value depending on whether it is called on |
| behalf of a "from ... import ..." statement or not. (This is caused |
| by the way the __import__ hook is used by the Python interpreter.) |
| """ |
| |
| |
| import builtins |
| import imp |
| import os |
| import sys |
| |
| __all__ = ["BasicModuleLoader","Hooks","ModuleLoader","FancyModuleLoader", |
| "BasicModuleImporter","ModuleImporter","install","uninstall"] |
| |
| VERBOSE = 0 |
| |
| |
| from imp import C_EXTENSION, PY_SOURCE, PY_COMPILED |
| from imp import C_BUILTIN, PY_FROZEN, PKG_DIRECTORY |
| BUILTIN_MODULE = C_BUILTIN |
| FROZEN_MODULE = PY_FROZEN |
| |
| |
| class _Verbose: |
| |
| def __init__(self, verbose = VERBOSE): |
| self.verbose = verbose |
| |
| def get_verbose(self): |
| return self.verbose |
| |
| def set_verbose(self, verbose): |
| self.verbose = verbose |
| |
| # XXX The following is an experimental interface |
| |
| def note(self, *args): |
| if self.verbose: |
| self.message(*args) |
| |
| def message(self, format, *args): |
| if args: |
| print(format%args) |
| else: |
| print(format) |
| |
| |
| class BasicModuleLoader(_Verbose): |
| |
| """Basic module loader. |
| |
| This provides the same functionality as built-in import. It |
| doesn't deal with checking sys.modules -- all it provides is |
| find_module() and a load_module(), as well as find_module_in_dir() |
| which searches just one directory, and can be overridden by a |
| derived class to change the module search algorithm when the basic |
| dependency on sys.path is unchanged. |
| |
| The interface is a little more convenient than imp's: |
| find_module(name, [path]) returns None or 'stuff', and |
| load_module(name, stuff) loads the module. |
| |
| """ |
| |
| def find_module(self, name, path = None): |
| if path is None: |
| path = [None] + self.default_path() |
| for dir in path: |
| stuff = self.find_module_in_dir(name, dir) |
| if stuff: return stuff |
| return None |
| |
| def default_path(self): |
| return sys.path |
| |
| def find_module_in_dir(self, name, dir): |
| if dir is None: |
| return self.find_builtin_module(name) |
| else: |
| try: |
| return imp.find_module(name, [dir]) |
| except ImportError: |
| return None |
| |
| def find_builtin_module(self, name): |
| # XXX frozen packages? |
| if imp.is_builtin(name): |
| return None, '', ('', '', BUILTIN_MODULE) |
| if imp.is_frozen(name): |
| return None, '', ('', '', FROZEN_MODULE) |
| return None |
| |
| def load_module(self, name, stuff): |
| file, filename, info = stuff |
| try: |
| return imp.load_module(name, file, filename, info) |
| finally: |
| if file: file.close() |
| |
| |
| class Hooks(_Verbose): |
| |
| """Hooks into the filesystem and interpreter. |
| |
| By deriving a subclass you can redefine your filesystem interface, |
| e.g. to merge it with the URL space. |
| |
| This base class behaves just like the native filesystem. |
| |
| """ |
| |
| # imp interface |
| def get_suffixes(self): return imp.get_suffixes() |
| def new_module(self, name): return imp.new_module(name) |
| def is_builtin(self, name): return imp.is_builtin(name) |
| def init_builtin(self, name): return imp.init_builtin(name) |
| def is_frozen(self, name): return imp.is_frozen(name) |
| def init_frozen(self, name): return imp.init_frozen(name) |
| def get_frozen_object(self, name): return imp.get_frozen_object(name) |
| def load_source(self, name, filename, file=None): |
| return imp.load_source(name, filename, file) |
| def load_compiled(self, name, filename, file=None): |
| return imp.load_compiled(name, filename, file) |
| def load_dynamic(self, name, filename, file=None): |
| return imp.load_dynamic(name, filename, file) |
| def load_package(self, name, filename, file=None): |
| return imp.load_module(name, file, filename, ("", "", PKG_DIRECTORY)) |
| |
| def add_module(self, name): |
| d = self.modules_dict() |
| if name in d: return d[name] |
| d[name] = m = self.new_module(name) |
| return m |
| |
| # sys interface |
| def modules_dict(self): return sys.modules |
| def default_path(self): return sys.path |
| |
| def path_split(self, x): return os.path.split(x) |
| def path_join(self, x, y): return os.path.join(x, y) |
| def path_isabs(self, x): return os.path.isabs(x) |
| # etc. |
| |
| def path_exists(self, x): return os.path.exists(x) |
| def path_isdir(self, x): return os.path.isdir(x) |
| def path_isfile(self, x): return os.path.isfile(x) |
| def path_islink(self, x): return os.path.islink(x) |
| # etc. |
| |
| def openfile(self, *x): return open(*x) |
| openfile_error = IOError |
| def listdir(self, x): return os.listdir(x) |
| listdir_error = os.error |
| # etc. |
| |
| |
| class ModuleLoader(BasicModuleLoader): |
| |
| """Default module loader; uses file system hooks. |
| |
| By defining suitable hooks, you might be able to load modules from |
| other sources than the file system, e.g. from compressed or |
| encrypted files, tar files or (if you're brave!) URLs. |
| |
| """ |
| |
| def __init__(self, hooks = None, verbose = VERBOSE): |
| BasicModuleLoader.__init__(self, verbose) |
| self.hooks = hooks or Hooks(verbose) |
| |
| def default_path(self): |
| return self.hooks.default_path() |
| |
| def modules_dict(self): |
| return self.hooks.modules_dict() |
| |
| def get_hooks(self): |
| return self.hooks |
| |
| def set_hooks(self, hooks): |
| self.hooks = hooks |
| |
| def find_builtin_module(self, name): |
| # XXX frozen packages? |
| if self.hooks.is_builtin(name): |
| return None, '', ('', '', BUILTIN_MODULE) |
| if self.hooks.is_frozen(name): |
| return None, '', ('', '', FROZEN_MODULE) |
| return None |
| |
| def find_module_in_dir(self, name, dir, allow_packages=1): |
| if dir is None: |
| return self.find_builtin_module(name) |
| if allow_packages: |
| fullname = self.hooks.path_join(dir, name) |
| if self.hooks.path_isdir(fullname): |
| stuff = self.find_module_in_dir("__init__", fullname, 0) |
| if stuff: |
| file = stuff[0] |
| if file: file.close() |
| return None, fullname, ('', '', PKG_DIRECTORY) |
| for info in self.hooks.get_suffixes(): |
| suff, mode, type = info |
| fullname = self.hooks.path_join(dir, name+suff) |
| try: |
| fp = self.hooks.openfile(fullname, mode) |
| return fp, fullname, info |
| except self.hooks.openfile_error: |
| pass |
| return None |
| |
| def load_module(self, name, stuff): |
| file, filename, info = stuff |
| (suff, mode, type) = info |
| try: |
| if type == BUILTIN_MODULE: |
| return self.hooks.init_builtin(name) |
| if type == FROZEN_MODULE: |
| return self.hooks.init_frozen(name) |
| if type == C_EXTENSION: |
| m = self.hooks.load_dynamic(name, filename, file) |
| elif type == PY_SOURCE: |
| m = self.hooks.load_source(name, filename, file) |
| elif type == PY_COMPILED: |
| m = self.hooks.load_compiled(name, filename, file) |
| elif type == PKG_DIRECTORY: |
| m = self.hooks.load_package(name, filename, file) |
| else: |
| raise ImportError("Unrecognized module type (%r) for %s" |
| % (type, name)) |
| finally: |
| if file: file.close() |
| m.__file__ = filename |
| return m |
| |
| |
| class FancyModuleLoader(ModuleLoader): |
| |
| """Fancy module loader -- parses and execs the code itself.""" |
| |
| def load_module(self, name, stuff): |
| file, filename, (suff, mode, type) = stuff |
| realfilename = filename |
| path = None |
| |
| if type == PKG_DIRECTORY: |
| initstuff = self.find_module_in_dir("__init__", filename, 0) |
| if not initstuff: |
| raise ImportError("No __init__ module in package %s" % name) |
| initfile, initfilename, initinfo = initstuff |
| initsuff, initmode, inittype = initinfo |
| if inittype not in (PY_COMPILED, PY_SOURCE): |
| if initfile: initfile.close() |
| raise ImportError("Bad type (%r) for __init__ module" |
| " in package %s" % (inittype, name)) |
| path = [filename] |
| file = initfile |
| realfilename = initfilename |
| type = inittype |
| |
| if type == FROZEN_MODULE: |
| code = self.hooks.get_frozen_object(name) |
| elif type == PY_COMPILED: |
| import marshal |
| file.seek(8) |
| code = marshal.load(file) |
| elif type == PY_SOURCE: |
| data = file.read() |
| code = compile(data, realfilename, 'exec') |
| else: |
| return ModuleLoader.load_module(self, name, stuff) |
| |
| m = self.hooks.add_module(name) |
| if path: |
| m.__path__ = path |
| m.__file__ = filename |
| try: |
| exec(code, m.__dict__) |
| except: |
| d = self.hooks.modules_dict() |
| if name in d: |
| del d[name] |
| raise |
| return m |
| |
| |
| class BasicModuleImporter(_Verbose): |
| |
| """Basic module importer; uses module loader. |
| |
| This provides basic import facilities but no package imports. |
| |
| """ |
| |
| def __init__(self, loader = None, verbose = VERBOSE): |
| _Verbose.__init__(self, verbose) |
| self.loader = loader or ModuleLoader(None, verbose) |
| self.modules = self.loader.modules_dict() |
| |
| def get_loader(self): |
| return self.loader |
| |
| def set_loader(self, loader): |
| self.loader = loader |
| |
| def get_hooks(self): |
| return self.loader.get_hooks() |
| |
| def set_hooks(self, hooks): |
| return self.loader.set_hooks(hooks) |
| |
| def import_module(self, name, globals={}, locals={}, fromlist=[]): |
| name = str(name) |
| if name in self.modules: |
| return self.modules[name] # Fast path |
| stuff = self.loader.find_module(name) |
| if not stuff: |
| raise ImportError("No module named %s" % name) |
| return self.loader.load_module(name, stuff) |
| |
| def reload(self, module, path = None): |
| name = str(module.__name__) |
| stuff = self.loader.find_module(name, path) |
| if not stuff: |
| raise ImportError("Module %s not found for reload" % name) |
| return self.loader.load_module(name, stuff) |
| |
| def unload(self, module): |
| del self.modules[str(module.__name__)] |
| # XXX Should this try to clear the module's namespace? |
| |
| def install(self): |
| self.save_import_module = builtins.__import__ |
| if not hasattr(builtins, 'unload'): |
| builtins.unload = None |
| self.save_unload = builtins.unload |
| builtins.__import__ = self.import_module |
| builtins.unload = self.unload |
| |
| def uninstall(self): |
| builtins.__import__ = self.save_import_module |
| builtins.unload = self.save_unload |
| if not builtins.unload: |
| del builtins.unload |
| |
| |
| class ModuleImporter(BasicModuleImporter): |
| |
| """A module importer that supports packages.""" |
| |
| def import_module(self, name, globals=None, locals=None, fromlist=None): |
| parent = self.determine_parent(globals) |
| q, tail = self.find_head_package(parent, str(name)) |
| m = self.load_tail(q, tail) |
| if not fromlist: |
| return q |
| if hasattr(m, "__path__"): |
| self.ensure_fromlist(m, fromlist) |
| return m |
| |
| def determine_parent(self, globals): |
| if not globals or not "__name__" in globals: |
| return None |
| pname = globals['__name__'] |
| if "__path__" in globals: |
| parent = self.modules[pname] |
| assert globals is parent.__dict__ |
| return parent |
| if '.' in pname: |
| i = pname.rfind('.') |
| pname = pname[:i] |
| parent = self.modules[pname] |
| assert parent.__name__ == pname |
| return parent |
| return None |
| |
| def find_head_package(self, parent, name): |
| if '.' in name: |
| i = name.find('.') |
| head = name[:i] |
| tail = name[i+1:] |
| else: |
| head = name |
| tail = "" |
| if parent: |
| qname = "%s.%s" % (parent.__name__, head) |
| else: |
| qname = head |
| q = self.import_it(head, qname, parent) |
| if q: return q, tail |
| if parent: |
| qname = head |
| parent = None |
| q = self.import_it(head, qname, parent) |
| if q: return q, tail |
| raise ImportError("No module named " + qname) |
| |
| def load_tail(self, q, tail): |
| m = q |
| while tail: |
| i = tail.find('.') |
| if i < 0: i = len(tail) |
| head, tail = tail[:i], tail[i+1:] |
| mname = "%s.%s" % (m.__name__, head) |
| m = self.import_it(head, mname, m) |
| if not m: |
| raise ImportError("No module named " + mname) |
| return m |
| |
| def ensure_fromlist(self, m, fromlist, recursive=0): |
| for sub in fromlist: |
| if sub == "*": |
| if not recursive: |
| try: |
| all = m.__all__ |
| except AttributeError: |
| pass |
| else: |
| self.ensure_fromlist(m, all, 1) |
| continue |
| if sub != "*" and not hasattr(m, sub): |
| subname = "%s.%s" % (m.__name__, sub) |
| submod = self.import_it(sub, subname, m) |
| if not submod: |
| raise ImportError("No module named " + subname) |
| |
| def import_it(self, partname, fqname, parent, force_load=0): |
| if not partname: |
| raise ValueError("Empty module name") |
| if not force_load: |
| try: |
| return self.modules[fqname] |
| except KeyError: |
| pass |
| try: |
| path = parent and parent.__path__ |
| except AttributeError: |
| return None |
| partname = str(partname) |
| stuff = self.loader.find_module(partname, path) |
| if not stuff: |
| return None |
| fqname = str(fqname) |
| m = self.loader.load_module(fqname, stuff) |
| if parent: |
| setattr(parent, partname, m) |
| return m |
| |
| def reload(self, module): |
| name = str(module.__name__) |
| if '.' not in name: |
| return self.import_it(name, name, None, force_load=1) |
| i = name.rfind('.') |
| pname = name[:i] |
| parent = self.modules[pname] |
| return self.import_it(name[i+1:], name, parent, force_load=1) |
| |
| |
| default_importer = None |
| current_importer = None |
| |
| def install(importer = None): |
| global current_importer |
| current_importer = importer or default_importer or ModuleImporter() |
| current_importer.install() |
| |
| def uninstall(): |
| global current_importer |
| current_importer.uninstall() |