summaryrefslogtreecommitdiffstats
path: root/Lib/ihooks.py
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1995-08-04 04:00:20 (GMT)
committerGuido van Rossum <guido@python.org>1995-08-04 04:00:20 (GMT)
commite7e578ffe042ca84f5b57e8056cb5598c7b44e5a (patch)
tree4a3b2e705ae78d7030280aef3f5839579755a698 /Lib/ihooks.py
parent40d1ea3b9ccf9e779f253685d1f5f6cf530945fa (diff)
downloadcpython-e7e578ffe042ca84f5b57e8056cb5598c7b44e5a.zip
cpython-e7e578ffe042ca84f5b57e8056cb5598c7b44e5a.tar.gz
cpython-e7e578ffe042ca84f5b57e8056cb5598c7b44e5a.tar.bz2
Initial revision
Diffstat (limited to 'Lib/ihooks.py')
-rw-r--r--Lib/ihooks.py365
1 files changed, 365 insertions, 0 deletions
diff --git a/Lib/ihooks.py b/Lib/ihooks.py
new file mode 100644
index 0000000..b379fc5
--- /dev/null
+++ b/Lib/ihooks.py
@@ -0,0 +1,365 @@
+"""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
+ 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)
+ 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:
+ 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):
+ 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()