371 lines
11 KiB
Python
371 lines
11 KiB
Python
"""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
|
|
(rimport.py), implementing a hierarchical module namespace (newimp.py)
|
|
or restricted execution (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 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 mporter 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().
|
|
|
|
XXX Should the imp.load_* functions also be called via the hooks
|
|
instance?
|
|
|
|
"""
|
|
|
|
|
|
import __builtin__
|
|
import imp
|
|
import os
|
|
import sys
|
|
|
|
|
|
from imp import C_EXTENSION, PY_SOURCE, PY_COMPILED
|
|
BUILTIN_MODULE = 32
|
|
FROZEN_MODULE = 33
|
|
|
|
|
|
class _Verbose:
|
|
|
|
def __init__(self, verbose = 0):
|
|
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:
|
|
apply(self.message, args)
|
|
|
|
def message(self, format, *args):
|
|
print format%args
|
|
|
|
|
|
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):
|
|
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, (suff, mode, type) = stuff
|
|
try:
|
|
if type == BUILTIN_MODULE:
|
|
return imp.init_builtin(name)
|
|
if type == FROZEN_MODULE:
|
|
return imp.init_frozen(name)
|
|
if type == C_EXTENSION:
|
|
return imp.load_dynamic(name, filename, file)
|
|
if type == PY_SOURCE:
|
|
return imp.load_source(name, filename, file)
|
|
if type == PY_COMPILED:
|
|
return imp.load_compiled(name, filename, file)
|
|
finally:
|
|
if file: file.close()
|
|
raise ImportError, "Unrecognized module type (%s) for %s" % \
|
|
(`type`, name)
|
|
|
|
|
|
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 add_module(self, name):
|
|
d = self.modules_dict()
|
|
if d.has_key(name): 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 apply(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 = 0):
|
|
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):
|
|
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):
|
|
if dir is None:
|
|
return self.find_builtin_module(name)
|
|
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, (suff, mode, type) = stuff
|
|
if type == BUILTIN_MODULE:
|
|
return self.hooks.init_builtin(name)
|
|
if type == FROZEN_MODULE:
|
|
return self.hooks.init_frozen(name)
|
|
if type == C_EXTENSION:
|
|
return self.hooks.load_dynamic(name, filename, file)
|
|
if type == PY_SOURCE:
|
|
return self.hooks.load_source(name, filename, file)
|
|
if type == PY_COMPILED:
|
|
return self.hooks.load_compiled(name, filename, file)
|
|
raise ImportError, "Unrecognized module type (%s) for %s" % \
|
|
(`type`, name)
|
|
|
|
|
|
class FancyModuleLoader(ModuleLoader):
|
|
|
|
"""Fancy module loader -- parses and execs the code itself."""
|
|
|
|
def load_module(self, name, stuff):
|
|
file, filename, (suff, mode, type) = stuff
|
|
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, filename, 'exec')
|
|
else:
|
|
return ModuleLoader.load_module(self, name, stuff)
|
|
m = self.hooks.add_module(name)
|
|
exec code in m.__dict__
|
|
return m
|
|
|
|
|
|
class ModuleImporter(_Verbose):
|
|
|
|
"""Default module importer; uses module loader.
|
|
|
|
This provides the same functionality as built-in import, when
|
|
combined with ModuleLoader.
|
|
|
|
"""
|
|
|
|
def __init__(self, loader = None, verbose = 0):
|
|
_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=[]):
|
|
if self.modules.has_key(name):
|
|
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 = 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[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
|
|
|
|
|
|
# XXX Some experimental hacks -- importing ihooks auto-installs!
|
|
# XXX (That's supposed to be transparent anyway...)
|
|
|
|
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()
|
|
|
|
|
|
install()
|