Package pyamf :: Package util :: Module imports

[hide private]
[frames] | no frames]

# Source Code for Module pyamf.util.imports

      1  # Copyright (c) 2007-2009 The PyAMF Project.
      2  # See LICENSE for details.
      3
      4  """
      5  Tools for doing dynamic imports
      6
      7  This module has been borrowed from the Importing package.
      8
      9  @see: U{http://pypi.python.org/pypi/Importing}
     10  @see: U{http://peak.telecommunity.com/DevCenter/Importing}
     11
     12  Original author: U{Phillip J. Eby<peak@eby-sarna.com>}
     13
     14  @since: 0.3.0
     15  """
     16
     17  __all__ = [
     18      'lazyModule', 'joinPath', 'whenImported', 'getModuleHooks',
     19  ]
     20
     21  import sys, os.path
     22  from types import ModuleType
     23
     24  postLoadHooks = {}
     25  loadedModules = []
     26
     27  try:
     28      from imp import find_module
     29
     30      # google app engine requires this because it checks to ensure that the
     31      # find_module function is functioning at least basically
     32      # most appengine patchers just stub the function
     33      find_module('pyamf.util.imports')
     34  except ImportError:
     35 -    def find_module(subname, path=None):
     36          # the dev_appserver freaks out if you have pyc, pyo in here as
     37          # we're hooking pyamf.amf0 and pyamf.amf3 in the gae adapter and
     38          # monkey-patching it. It rightly complains as the byte-compiled module
     39          # is different to the 'final' module.
     40          PY_EXT = ('.py',)
     41
     42          if path is None:
     43              path = sys.path
     44
     45          for p in path:
     46              py = os.path.join(p, subname)
     47
     48              for full in PY_EXT:
     49                  full = py + full
     50
     51                  if os.path.exists(full):
     52                      return open(full), full, None
     53
     54              py = os.path.join(p, subname, '__init__')
     55
     56              for full in PY_EXT:
     57                  full = py + full
     58
     59                  if os.path.exists(full):
     60                      return None, os.path.join(p, subname), None
     61
     62          raise ImportError('No module named %s' % subname)
     63
     64 -class SubModuleLoadHook(object):
     65 -    def __init__(self, parent, child, hook, *args, **kwargs):
     66          self.parent = parent
     67          self.child = child
     68          self.hook = hook
     69          self.args = args
     70          self.kwargs = kwargs
     71
     72 -    def __eq__(self, other):
     73          if not isinstance(other, SubModuleLoadHook):
     74              return False
     75
     76          return self.parent == other.parent and self.child == other.child
     77
     78 -    def __call__(self, module):
     79          return self.hook(*self.args, **self.kwargs)
     80
     81 -class AlreadyRead(Exception):
     82      pass
     83
     84 -class LazyModule(ModuleType):
     85      __slots__ = ()
     86      __reserved_attrs__ = ('__name__', '__file__', '__path__')
     87
     88 -    def __init__(self, name, file, path=None):
     89          ModuleType.__setattr__(self, '__name__', name)
     90          ModuleType.__setattr__(self, '__file__', file)
     91
     92          if path is not None:
     93              ModuleType.__setattr__(self, '__path__', path)
     94
     95 -    def __getattribute__(self, attr):
     96          if attr not in LazyModule.__reserved_attrs__:
     97              _loadModule(self)
     98
     99          return ModuleType.__getattribute__(self, attr)
    100
    101 -    def __setattr__(self, attr, value):
    102          if attr not in LazyModule.__reserved_attrs__:
    103              _loadModule(self)
    104
    105          return ModuleType.__setattr__(self, attr, value)
    106
    107 -def _loadModule(module):
    108      if _isLazy(module) and module not in loadedModules:
    109          _loadAndRunHooks(module)
    110
    111 -def joinPath(modname, relativePath):
    112      """
    113      Adjust a module name by a '/'-separated, relative or absolute path
    114      """
    115      module = modname.split('.')
    116
    117      for p in relativePath.split('/'):
    118          if p == '..':
    119              module.pop()
    120          elif not p:
    121              module = []
    122          elif p != '.':
    123              module.append(p)
    124
    125      return '.'.join(module)
    126
    127 -def lazyModule(modname, relativePath=None):
    128      """
    129      Return module 'modname', but with its contents loaded "on demand"
    130
    131      This function returns 'sys.modules[modname]', if present.  Otherwise
    132      it creates a 'LazyModule' object for the specified module, caches it
    133      in 'sys.modules', and returns it.
    134
    135      'LazyModule' is a subclass of the standard Python module type, that
    136      remains empty until an attempt is made to access one of its
    137      attributes.  At that moment, the module is loaded into memory, and
    138      any hooks that were defined via 'whenImported()' are invoked.
    139
    140      Note that calling 'lazyModule' with the name of a non-existent or
    141      unimportable module will delay the 'ImportError' until the moment
    142      access is attempted.  The 'ImportError' will occur every time an
    143      attribute access is attempted, until the problem is corrected.
    144
    145      This function also takes an optional second parameter, 'relativePath',
    146      which will be interpreted as a '/'-separated path string relative to
    147      'modname'.  If a 'relativePath' is supplied, the module found by
    148      traversing the path will be loaded instead of 'modname'.  In the path,
    149      '.' refers to the current module, and '..' to the current module's
    150      parent.  For example::
    151
    152          fooBaz = lazyModule('foo.bar','../baz')
    153
    154      will return the module 'foo.baz'.  The main use of the 'relativePath'
    155      feature is to allow relative imports in modules that are intended for
    156      use with module inheritance.  Where an absolute import would be carried
    157      over as-is into the inheriting module, an import relative to '__name__'
    158      will be relative to the inheriting module, e.g.::
    159
    160          something = lazyModule(__name__,'../path/to/something')
    161
    162      The above code will have different results in each module that inherits
    163      it.
    164
    165      (Note: 'relativePath' can also be an absolute path (starting with '/');
    166      this is mainly useful for module '__bases__' lists.)
    167      """
    168      if relativePath:
    169          modname = joinPath(modname, relativePath)
    170
    171      if modname not in sys.modules:
    172          file_name = path = None
    173
    174          if '.' in modname:
    175              splitpos = modname.rindex('.')
    176
    177              parent = sys.modules[modname[:splitpos]]
    178              file_name = find_module(modname[splitpos + 1:], parent.__path__)[1]
    179          else:
    180              file_name = find_module(modname)[1]
    181
    182          if os.path.isdir(file_name):
    183              path = [file_name]
    184              py = os.path.join(file_name, '__init__')
    185
    186              for full in ('.pyo', '.pyc', '.py'):
    187                  full = py + full
    188
    189                  if os.path.exists(full):
    190                      break
    191              else:
    192                  raise ImportError('No module name %d' % modname)
    193
    194              file_name = full
    195
    196          getModuleHooks(modname) # force an empty hook list into existence
    197          sys.modules[modname] = LazyModule(modname, file_name, path)
    198
    199          if '.' in modname:
    200              # ensure parent module/package is in sys.modules
    201              # and parent.modname=module, as soon as the parent is imported
    202
    203              splitpos = modname.rindex('.')
    204
    205              whenImported(
    206                  modname[:splitpos],
    207                  lambda m: setattr(m, modname[splitpos + 1:], sys.modules[modname])
    208              )
    209
    210      return sys.modules[modname]
    211
    212 -def _isLazy(module):
    213      """
    214      Checks to see if the supplied C{module} is lazy
    215      """
    216      if module.__name__ not in postLoadHooks.keys():
    217          return False
    218
    219      return postLoadHooks[module.__name__] is not None
    220
    221 -def _loadAndRunHooks(module):
    222      """
    223      Load an unactivated "lazy" module object
    224      """
    225      if _isLazy(module): # don't reload if already loaded!
    226          loadedModules.append(module)
    227          reload(module)
    228
    229      try:
    230          for hook in getModuleHooks(module.__name__):
    231              hook(module)
    232      finally:
    233          # Ensure hooks are not called again, even if they fail
    234          postLoadHooks[module.__name__] = None
    235
    236 -def getModuleHooks(moduleName):
    237      """
    238      Get list of hooks for 'moduleName'; error if module already loaded
    239      """
    240      hooks = postLoadHooks.setdefault(moduleName, [])
    241
    242      if hooks is None:
    243          raise AlreadyRead("Module already imported", moduleName)
    244
    245      return hooks
    246
    247 -def _setModuleHook(moduleName, hook):
    248      if moduleName in sys.modules and postLoadHooks.get(moduleName) is None:
    249          # Module is already imported/loaded, just call the hook
    250          module = sys.modules[moduleName]
    251          hook(module)
    252
    253          return module
    254
    255      getModuleHooks(moduleName).append(hook)
    256
    257      return lazyModule(moduleName)
    258
    259 -def whenImported(moduleName, hook):
    260      """
    261      Call 'hook(module)' when module named 'moduleName' is first used
    262
    263      'hook' must accept one argument: the module object named by 'moduleName',
    264      which must be a fully qualified (i.e. absolute) module name.  The hook
    265      should not raise any exceptions, or it may prevent later hooks from
    266      running.
    267
    268      If the module has already been imported normally, 'hook(module)' is
    269      called immediately, and the module object is returned from this function.
    270      If the module has not been imported, or has only been imported lazily,
    271      then the hook is called when the module is first used, and a lazy import
    272      of the module is returned from this function.  If the module was imported
    273      lazily and used before calling this function, the hook is called
    274      immediately, and the loaded module is returned from this function.
    275
    276      Note that using this function implies a possible lazy import of the
    277      specified module, and lazy importing means that any 'ImportError' will be
    278      deferred until the module is used.
    279      """
    280      if '.' in moduleName:
    281          # If parent is not yet imported, delay hook installation until the
    282          # parent is imported.
    283          splitpos = moduleName.rindex('.')
    284
    285          sub_hook = SubModuleLoadHook(moduleName[:splitpos],
    286              moduleName[splitpos + 1:], _setModuleHook, moduleName, hook)
    287
    288          if moduleName[:splitpos] not in postLoadHooks.keys():
    289              whenImported(moduleName[:splitpos], sub_hook)
    290          elif postLoadHooks[moduleName[:splitpos]] is None:
    291              whenImported(moduleName[:splitpos], sub_hook)
    292          elif sub_hook not in postLoadHooks[moduleName[:splitpos]]:
    293              whenImported(moduleName[:splitpos], sub_hook)
    294          else:
    295              postLoadHooks[moduleName[:splitpos]].append(sub_hook)
    296      else:
    297          return _setModuleHook(moduleName, hook)
    298