123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- """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().
- """
- 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):
- 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()
|