| # Copyright (c) The PyAMF Project. |
| # See LICENSE.txt for details. |
| |
| """ |
| Tools for doing dynamic imports. |
| |
| @since: 0.3 |
| """ |
| |
| import sys |
| |
| |
| __all__ = ['when_imported'] |
| |
| |
| def when_imported(name, *hooks): |
| """ |
| Call C{hook(module)} when module named C{name} is first imported. C{name} |
| must be a fully qualified (i.e. absolute) module name. |
| |
| C{hook} must accept one argument: which will be the imported module object. |
| |
| If the module has already been imported, 'hook(module)' is called |
| immediately, and the module object is returned from this function. If the |
| module has not been imported, then the hook is called when the module is |
| first imported. |
| """ |
| global finder |
| |
| finder.when_imported(name, *hooks) |
| |
| |
| class ModuleFinder(object): |
| """ |
| This is a special module finder object that executes a collection of |
| callables when a specific module has been imported. An instance of this |
| is placed in C{sys.meta_path}, which is consulted before C{sys.modules} - |
| allowing us to provide this functionality. |
| |
| @ivar post_load_hooks: C{dict} of C{full module path -> callable} to be |
| executed when the module is imported. |
| @ivar loaded_modules: C{list} of modules that this finder has seen. Used |
| to stop recursive imports in L{load_module} |
| @see: L{when_imported} |
| @since: 0.5 |
| """ |
| |
| def __init__(self): |
| self.post_load_hooks = {} |
| self.loaded_modules = [] |
| |
| def find_module(self, name, path=None): |
| """ |
| Called when an import is made. If there are hooks waiting for this |
| module to be imported then we stop the normal import process and |
| manually load the module. |
| |
| @param name: The name of the module being imported. |
| @param path The root path of the module (if a package). We ignore this. |
| @return: If we want to hook this module, we return a C{loader} |
| interface (which is this instance again). If not we return C{None} |
| to allow the standard import process to continue. |
| """ |
| if name in self.loaded_modules: |
| return None |
| |
| hooks = self.post_load_hooks.get(name, None) |
| |
| if hooks: |
| return self |
| |
| def load_module(self, name): |
| """ |
| If we get this far, then there are hooks waiting to be called on |
| import of this module. We manually load the module and then run the |
| hooks. |
| |
| @param name: The name of the module to import. |
| """ |
| self.loaded_modules.append(name) |
| |
| try: |
| __import__(name, {}, {}, []) |
| |
| mod = sys.modules[name] |
| self._run_hooks(name, mod) |
| except: |
| self.loaded_modules.pop() |
| |
| raise |
| |
| return mod |
| |
| def when_imported(self, name, *hooks): |
| """ |
| @see: L{when_imported} |
| """ |
| if name in sys.modules: |
| for hook in hooks: |
| hook(sys.modules[name]) |
| |
| return |
| |
| h = self.post_load_hooks.setdefault(name, []) |
| h.extend(hooks) |
| |
| def _run_hooks(self, name, module): |
| """ |
| Run all hooks for a module. |
| """ |
| hooks = self.post_load_hooks.pop(name, []) |
| |
| for hook in hooks: |
| hook(module) |
| |
| def __getstate__(self): |
| return (self.post_load_hooks.copy(), self.loaded_modules[:]) |
| |
| def __setstate__(self, state): |
| self.post_load_hooks, self.loaded_modules = state |
| |
| |
| def _init(): |
| """ |
| Internal function to install the module finder. |
| """ |
| global finder |
| |
| if finder is None: |
| finder = ModuleFinder() |
| |
| if finder not in sys.meta_path: |
| sys.meta_path.insert(0, finder) |
| |
| |
| finder = None |
| _init() |