| """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, or restricted execution by rexec.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.) It | |
| would also do wise to install a different version of reload(). | |
| """ | |
| from warnings import warnpy3k, warn | |
| warnpy3k("the ihooks module has been removed in Python 3.0", stacklevel=2) | |
| del warnpy3k | |
| import __builtin__ | |
| 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 in 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 = __builtin__.__import__ | |
| self.save_reload = __builtin__.reload | |
| if not hasattr(__builtin__, 'unload'): | |
| __builtin__.unload = None | |
| self.save_unload = __builtin__.unload | |
| __builtin__.__import__ = self.import_module | |
| __builtin__.reload = self.reload | |
| __builtin__.unload = self.unload | |
| def uninstall(self): | |
| __builtin__.__import__ = self.save_import_module | |
| __builtin__.reload = self.save_reload | |
| __builtin__.unload = self.save_unload | |
| if not __builtin__.unload: | |
| del __builtin__.unload | |
| class ModuleImporter(BasicModuleImporter): | |
| """A module importer that supports packages.""" | |
| def import_module(self, name, globals=None, locals=None, fromlist=None, | |
| level=-1): | |
| parent = self.determine_parent(globals, level) | |
| 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, level=-1): | |
| if not globals or not level: | |
| return None | |
| pkgname = globals.get('__package__') | |
| if pkgname is not None: | |
| if not pkgname and level > 0: | |
| raise ValueError, 'Attempted relative import in non-package' | |
| else: | |
| # __package__ not set, figure it out and set it | |
| modname = globals.get('__name__') | |
| if modname is None: | |
| return None | |
| if "__path__" in globals: | |
| # __path__ is set so modname is already the package name | |
| pkgname = modname | |
| else: | |
| # normal module, work out package name if any | |
| if '.' not in modname: | |
| if level > 0: | |
| raise ValueError, ('Attempted relative import in ' | |
| 'non-package') | |
| globals['__package__'] = None | |
| return None | |
| pkgname = modname.rpartition('.')[0] | |
| globals['__package__'] = pkgname | |
| if level > 0: | |
| dot = len(pkgname) | |
| for x in range(level, 1, -1): | |
| try: | |
| dot = pkgname.rindex('.', 0, dot) | |
| except ValueError: | |
| raise ValueError('attempted relative import beyond ' | |
| 'top-level package') | |
| pkgname = pkgname[:dot] | |
| try: | |
| return sys.modules[pkgname] | |
| except KeyError: | |
| if level < 1: | |
| warn("Parent module '%s' not found while handling " | |
| "absolute import" % pkgname, RuntimeWarning, 1) | |
| return None | |
| else: | |
| raise SystemError, ("Parent module '%s' not loaded, cannot " | |
| "perform relative import" % pkgname) | |
| 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 '%s'" % 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 '%s'" % 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 '%s'" % subname | |
| def import_it(self, partname, fqname, parent, force_load=0): | |
| if not partname: | |
| # completely empty module name should only happen in | |
| # 'from . import' or __import__("") | |
| return parent | |
| 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() |