Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/peak/util/imports.py
diff options
context:
space:
mode:
Diffstat (limited to 'peak/util/imports.py')
-rw-r--r--peak/util/imports.py410
1 files changed, 410 insertions, 0 deletions
diff --git a/peak/util/imports.py b/peak/util/imports.py
new file mode 100644
index 0000000..36ab04d
--- /dev/null
+++ b/peak/util/imports.py
@@ -0,0 +1,410 @@
+"""Tools for doing dynamic imports"""
+
+__all__ = [
+ 'importString', 'importObject', 'importSequence', 'importSuite',
+ 'lazyModule', 'joinPath', 'whenImported', 'getModuleHooks',
+]
+
+import __main__, sys
+
+from types import StringTypes, ModuleType
+from sys import modules
+from imp import acquire_lock, release_lock
+
+defaultGlobalDict = __main__.__dict__
+
+try:
+ from peak.util.EigenData import AlreadyRead
+except ImportError:
+ class AlreadyRead(Exception):pass
+
+
+def importSuite(specs, globalDict=defaultGlobalDict):
+ """Create a test suite from import specs"""
+
+ from unittest import TestSuite
+
+ return TestSuite(
+ [t() for t in importSequence(specs,globalDict)]
+ )
+
+
+
+
+
+
+
+
+
+
+
+
+def joinPath(modname, relativePath):
+ """Adjust a module name by a '/'-separated, relative or absolute path"""
+
+ module = modname.split('.')
+ for p in relativePath.split('/'):
+
+ if p=='..':
+ module.pop()
+ elif not p:
+ module = []
+ elif p!='.':
+ module.append(p)
+
+ return '.'.join(module)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def importString(name, globalDict=defaultGlobalDict):
+ """Import an item specified by a string
+
+ Example Usage::
+
+ attribute1 = importString('some.module:attribute1')
+ attribute2 = importString('other.module:nested.attribute2')
+
+ 'importString' imports an object from a module, according to an
+ import specification string: a dot-delimited path to an object
+ in the Python package namespace. For example, the string
+ '"some.module.attribute"' is equivalent to the result of
+ 'from some.module import attribute'.
+
+ For readability of import strings, it's sometimes helpful to use a ':' to
+ separate a module name from items it contains. It's optional, though,
+ as 'importString' will convert the ':' to a '.' internally anyway."""
+
+ if ':' in name:
+ name = name.replace(':','.')
+
+ parts = filter(None,name.split('.'))
+ item = __import__(parts.pop(0), globalDict, globalDict, ['__name__'])
+
+ # Fast path for the common case, where everything is imported already
+ for attr in parts:
+ try:
+ item = getattr(item, attr)
+ except AttributeError:
+ break # either there's an error, or something needs importing
+ else:
+ return item
+
+ # We couldn't get there with just getattrs from the base import. So now
+ # we loop *backwards* trying to import longer names, then shorter, until
+ # we find the longest possible name that can be handled with __import__,
+ # then loop forward again with getattr. This lets us give more meaningful
+ # error messages than if we only went forwards.
+ attrs = []
+ exc = None
+
+ try:
+ while True:
+ try:
+ # Exit as soon as we find a prefix of the original `name`
+ # that's an importable *module* or package
+ item = __import__(name, globalDict, globalDict, ['__name__'])
+ break
+ except ImportError:
+ if not exc:
+ # Save the first ImportError, as it's usually the most
+ # informative, especially w/Python < 2.4
+ exc = sys.exc_info()
+
+ if '.' not in name:
+ # We've backed up all the way to the beginning, so reraise
+ # the first ImportError we got
+ raise exc[0],exc[1],exc[2]
+
+ # Otherwise back up one position and try again
+ parts = name.split('.')
+ attrs.append(parts[-1])
+ name = '.'.join(parts[:-1])
+ finally:
+ exc = None
+
+ # Okay, the module object is now in 'item', so we can just loop forward
+ # to retrieving the desired attribute.
+ #
+ while attrs:
+ attr = attrs.pop()
+ try:
+ item = getattr(item,attr)
+ except AttributeError:
+ raise ImportError("%r has no %r attribute" % (item,attr))
+
+ return item
+
+
+
+
+
+def lazyModule(modname, relativePath=None):
+
+ """Return module 'modname', but with its contents loaded "on demand"
+
+ This function returns 'sys.modules[modname]', if present. Otherwise
+ it creates a 'LazyModule' object for the specified module, caches it
+ in 'sys.modules', and returns it.
+
+ 'LazyModule' is a subclass of the standard Python module type, that
+ remains empty until an attempt is made to access one of its
+ attributes. At that moment, the module is loaded into memory, and
+ any hooks that were defined via 'whenImported()' are invoked.
+
+ Note that calling 'lazyModule' with the name of a non-existent or
+ unimportable module will delay the 'ImportError' until the moment
+ access is attempted. The 'ImportError' will occur every time an
+ attribute access is attempted, until the problem is corrected.
+
+ This function also takes an optional second parameter, 'relativePath',
+ which will be interpreted as a '/'-separated path string relative to
+ 'modname'. If a 'relativePath' is supplied, the module found by
+ traversing the path will be loaded instead of 'modname'. In the path,
+ '.' refers to the current module, and '..' to the current module's
+ parent. For example::
+
+ fooBaz = lazyModule('foo.bar','../baz')
+
+ will return the module 'foo.baz'. The main use of the 'relativePath'
+ feature is to allow relative imports in modules that are intended for
+ use with module inheritance. Where an absolute import would be carried
+ over as-is into the inheriting module, an import relative to '__name__'
+ will be relative to the inheriting module, e.g.::
+
+ something = lazyModule(__name__,'../path/to/something')
+
+ The above code will have different results in each module that inherits
+ it.
+
+ (Note: 'relativePath' can also be an absolute path (starting with '/');
+ this is mainly useful for module '__bases__' lists.)"""
+
+ def _loadModule(module):
+ oldGA = LazyModule.__getattribute__
+ oldSA = LazyModule.__setattr__
+
+ modGA = ModuleType.__getattribute__
+ modSA = ModuleType.__setattr__
+
+ LazyModule.__getattribute__ = modGA
+ LazyModule.__setattr__ = modSA
+
+ acquire_lock()
+ try:
+ try:
+ # don't reload if already loaded!
+ if module.__dict__.keys()==['__name__']:
+ # Get Python to do the real import!
+ reload(module)
+ try:
+ for hook in getModuleHooks(module.__name__):
+ hook(module)
+ finally:
+ # Ensure hooks are not called again, even if they fail
+ postLoadHooks[module.__name__] = None
+ except:
+ # Reset our state so that we can retry later
+ if '__file__' not in module.__dict__:
+ LazyModule.__getattribute__ = oldGA.im_func
+ LazyModule.__setattr__ = oldSA.im_func
+ raise
+
+ try:
+ # Convert to a real module (if under 2.2)
+ module.__class__ = ModuleType
+ except TypeError:
+ pass # 2.3 will fail, but no big deal
+
+ finally:
+ release_lock()
+
+
+
+ class LazyModule(ModuleType):
+ __slots__ = ()
+ def __init__(self, name):
+ ModuleType.__setattr__(self,'__name__',name)
+ #super(LazyModule,self).__init__(name)
+
+ def __getattribute__(self,attr):
+ _loadModule(self)
+ return ModuleType.__getattribute__(self,attr)
+
+ def __setattr__(self,attr,value):
+ _loadModule(self)
+ return ModuleType.__setattr__(self,attr,value)
+
+ if relativePath:
+ modname = joinPath(modname, relativePath)
+
+ acquire_lock()
+ try:
+ if modname not in modules:
+ getModuleHooks(modname) # force an empty hook list into existence
+ modules[modname] = LazyModule(modname)
+ if '.' in modname:
+ # ensure parent module/package is in sys.modules
+ # and parent.modname=module, as soon as the parent is imported
+ splitpos = modname.rindex('.')
+ whenImported(
+ modname[:splitpos],
+ lambda m: setattr(m,modname[splitpos+1:],modules[modname])
+ )
+ return modules[modname]
+ finally:
+ release_lock()
+
+
+postLoadHooks = {}
+
+
+
+
+
+def getModuleHooks(moduleName):
+
+ """Get list of hooks for 'moduleName'; error if module already loaded"""
+
+ acquire_lock()
+ try:
+ hooks = postLoadHooks.setdefault(moduleName,[])
+ if hooks is None:
+ raise AlreadyRead("Module already imported", moduleName)
+ return hooks
+ finally:
+ release_lock()
+
+
+def _setModuleHook(moduleName, hook):
+ acquire_lock()
+ try:
+ if moduleName in modules and postLoadHooks.get(moduleName) is None:
+ # Module is already imported/loaded, just call the hook
+ module = modules[moduleName]
+ hook(module)
+ return module
+
+ getModuleHooks(moduleName).append(hook)
+ return lazyModule(moduleName)
+ finally:
+ release_lock()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def whenImported(moduleName, hook=None):
+
+ """Call 'hook(module)' when module named 'moduleName' is first used
+
+ 'hook' must accept one argument: the module object named by 'moduleName',
+ which must be a fully qualified (i.e. absolute) module name. The hook
+ should not raise any exceptions, or it may prevent later hooks from
+ running.
+
+ If the module has already been imported normally, 'hook(module)' is
+ called immediately, and the module object is returned from this function.
+ If the module has not been imported, or has only been imported lazily,
+ then the hook is called when the module is first used, and a lazy import
+ of the module is returned from this function. If the module was imported
+ lazily and used before calling this function, the hook is called
+ immediately, and the loaded module is returned from this function.
+
+ Note that using this function implies a possible lazy import of the
+ specified module, and lazy importing means that any 'ImportError' will be
+ deferred until the module is used.
+ """
+ if hook is None:
+ def decorate(func):
+ whenImported(moduleName, func)
+ return func
+ return decorate
+
+ if '.' in moduleName:
+ # If parent is not yet imported, delay hook installation until the
+ # parent is imported.
+ splitpos = moduleName.rindex('.')
+ whenImported(
+ moduleName[:splitpos], lambda m: _setModuleHook(moduleName,hook)
+ )
+ else:
+ return _setModuleHook(moduleName,hook)
+
+
+
+
+
+def importObject(spec, globalDict=defaultGlobalDict):
+
+ """Convert a possible string specifier to an object
+
+ If 'spec' is a string or unicode object, import it using 'importString()',
+ otherwise return it as-is.
+ """
+
+ if isinstance(spec,StringTypes):
+ return importString(spec, globalDict)
+
+ return spec
+
+
+def importSequence(specs, globalDict=defaultGlobalDict):
+
+ """Convert a string or list specifier to a list of objects.
+
+ If 'specs' is a string or unicode object, treat it as a
+ comma-separated list of import specifications, and return a
+ list of the imported objects.
+
+ If the result is not a string but is iterable, return a list
+ with any string/unicode items replaced with their corresponding
+ imports.
+ """
+
+ if isinstance(specs,StringTypes):
+ return [importString(x.strip(),globalDict) for x in specs.split(',')]
+ else:
+ return [importObject(s,globalDict) for s in specs]
+
+
+
+
+
+
+
+
+
+