236 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
	
	
| """
 | |
| package initialization.
 | |
| 
 | |
| You use the functionality of this package by putting
 | |
| 
 | |
|     from py.initpkg import initpkg
 | |
|     initpkg(__name__, exportdefs={
 | |
|         'name1.name2' : ('./path/to/file.py', 'name')
 | |
|         ...
 | |
|     })
 | |
| 
 | |
| into your package's __init__.py file.  This will
 | |
| lead your package to only expose the names of all
 | |
| your implementation files that you explicitely
 | |
| specify.  In the above example 'name1' will
 | |
| become a Module instance where 'name2' is
 | |
| bound in its namespace to the 'name' object
 | |
| in the relative './path/to/file.py' python module.
 | |
| Note that you can also use a '.c' file in which
 | |
| case it will be compiled via distutils-facilities
 | |
| on the fly.
 | |
| 
 | |
| """
 | |
| from __future__ import generators
 | |
| import sys
 | |
| import os
 | |
| assert sys.version_info >= (2,2,0), "py lib requires python 2.2 or higher"
 | |
| from types import ModuleType
 | |
| 
 | |
| # ---------------------------------------------------
 | |
| # Package Object
 | |
| # ---------------------------------------------------
 | |
| 
 | |
| class Package(object):
 | |
|     def __init__(self, name, exportdefs, metainfo):
 | |
|         pkgmodule = sys.modules[name]
 | |
|         assert pkgmodule.__name__ == name
 | |
|         self.name = name
 | |
|         self.exportdefs = exportdefs
 | |
|         self.module = pkgmodule
 | |
|         assert not hasattr(pkgmodule, '__pkg__'), \
 | |
|                    "unsupported reinitialization of %r" % pkgmodule
 | |
|         pkgmodule.__pkg__ = self
 | |
| 
 | |
|         # make available pkgname.__
 | |
|         implname = name + '.' + '__'
 | |
|         self.implmodule = ModuleType(implname)
 | |
|         self.implmodule.__name__ = implname
 | |
|         self.implmodule.__file__ = os.path.abspath(pkgmodule.__file__)
 | |
|         self.implmodule.__path__ = [os.path.abspath(p)
 | |
|                                     for p in pkgmodule.__path__]
 | |
|         pkgmodule.__ = self.implmodule
 | |
|         setmodule(implname, self.implmodule)
 | |
|         # inhibit further direct filesystem imports through the package module
 | |
|         del pkgmodule.__path__
 | |
| 
 | |
|         # set metainfo 
 | |
|         for name, value in metainfo.items(): 
 | |
|             setattr(self, name, value) 
 | |
|         version = metainfo.get('version', None)
 | |
|         if version:
 | |
|             pkgmodule.__version__ = version
 | |
| 
 | |
|     def _resolve(self, extpyish):
 | |
|         """ resolve a combined filesystem/python extpy-ish path. """
 | |
|         fspath, modpath = extpyish
 | |
|         assert fspath.startswith('./'), \
 | |
|                "%r is not an implementation path (XXX)" % (extpyish,)
 | |
|         implmodule = self._loadimpl(fspath[:-3])
 | |
|         if not isinstance(modpath, str): # export the entire module
 | |
|             return implmodule
 | |
| 
 | |
|         current = implmodule
 | |
|         for x in modpath.split('.'):
 | |
|             try: 
 | |
|                 current = getattr(current, x)
 | |
|             except AttributeError: 
 | |
|                 raise AttributeError("resolving %r failed: %s" %(
 | |
|                                      extpyish, x))
 | |
| 
 | |
|         return current
 | |
| 
 | |
|     def getimportname(self, path): 
 | |
|         if not path.ext.startswith('.py'): 
 | |
|             return None 
 | |
|         import py
 | |
|         base = py.path.local(self.implmodule.__file__).dirpath()
 | |
|         if not path.relto(base): 
 | |
|             return None 
 | |
|         names = path.new(ext='').relto(base).split(path.sep) 
 | |
|         dottedname = ".".join([self.implmodule.__name__] + names)
 | |
|         return dottedname 
 | |
| 
 | |
|     def _loadimpl(self, relfile):
 | |
|         """ load implementation for the given relfile. """
 | |
|         parts = [x.strip() for x in relfile.split('/') if x and x!= '.']
 | |
|         modpath = ".".join([self.implmodule.__name__] + parts)
 | |
|         #print "trying import", modpath
 | |
|         return __import__(modpath, None, None, ['__doc__'])
 | |
| 
 | |
|     def exportitems(self):
 | |
|         return self.exportdefs.items()
 | |
| 
 | |
|     def getpath(self):
 | |
|         from py.path import local
 | |
|         base = local(self.implmodule.__file__).dirpath()
 | |
|         assert base.check()
 | |
|         return base
 | |
| 
 | |
| def setmodule(modpath, module):
 | |
|     #print "sys.modules[%r] = %r" % (modpath, module)
 | |
|     sys.modules[modpath] = module
 | |
| 
 | |
| # ---------------------------------------------------
 | |
| # API Module Object
 | |
| # ---------------------------------------------------
 | |
| 
 | |
| class ApiModule(ModuleType):
 | |
|     def __init__(self, pkg, name):
 | |
|         self.__map__ = {}
 | |
|         self.__pkg__ = pkg
 | |
|         self.__name__ = name
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return '<ApiModule %r>' % (self.__name__,)
 | |
| 
 | |
|     def __getattr__(self, name):
 | |
|         if '*' in self.__map__: 
 | |
|             extpy = self.__map__['*'][0], name 
 | |
|             result = self.__pkg__._resolve(extpy) 
 | |
|         else: 
 | |
|             try:
 | |
|                 extpy = self.__map__.pop(name)
 | |
|             except KeyError:
 | |
|                 __tracebackhide__ = True
 | |
|                 raise AttributeError(name)
 | |
|             else: 
 | |
|                 result = self.__pkg__._resolve(extpy) 
 | |
| 
 | |
|         setattr(self, name, result)
 | |
|         #self._fixinspection(result, name) 
 | |
|         return result
 | |
| 
 | |
|     def __setattr__(self, name, value):
 | |
|         super(ApiModule, self).__setattr__(name, value)
 | |
|         try:
 | |
|             del self.__map__[name]
 | |
|         except KeyError:
 | |
|             pass
 | |
| 
 | |
|     def _deprecated_fixinspection(self, result, name): 
 | |
|         # modify some attrs to make a class appear at export level 
 | |
|         if hasattr(result, '__module__'):
 | |
|             if not result.__module__.startswith('py.__'):
 | |
|                 return   # don't change __module__ nor __name__ for classes
 | |
|                          # that the py lib re-exports from somewhere else,
 | |
|                          # e.g. py.builtin.BaseException
 | |
|             try:
 | |
|                 setattr(result, '__module__', self.__name__)
 | |
|             except (AttributeError, TypeError):
 | |
|                 pass
 | |
|         if hasattr(result, '__bases__'):
 | |
|             try:
 | |
|                 setattr(result, '__name__', name)
 | |
|             except (AttributeError, TypeError):
 | |
|                 pass
 | |
| 
 | |
|     def getdict(self):
 | |
|         # force all the content of the module to be loaded when __dict__ is read
 | |
|         dictdescr = ModuleType.__dict__['__dict__']
 | |
|         dict = dictdescr.__get__(self)
 | |
|         if dict is not None:
 | |
|             if '*' not in self.__map__: 
 | |
|                 for name in list(self.__map__):
 | |
|                     hasattr(self, name)  # force attribute load, ignore errors
 | |
|                 assert not self.__map__, "%r not empty" % self.__map__
 | |
|             else: 
 | |
|                 fsname = self.__map__['*'][0] 
 | |
|                 dict.update(self.__pkg__._loadimpl(fsname[:-3]).__dict__)
 | |
|         return dict
 | |
| 
 | |
|     __dict__ = property(getdict)
 | |
|     del getdict
 | |
| 
 | |
| # ---------------------------------------------------
 | |
| # Bootstrap Virtual Module Hierarchy
 | |
| # ---------------------------------------------------
 | |
| 
 | |
| def initpkg(pkgname, exportdefs, **kw):
 | |
|     #print "initializing package", pkgname
 | |
|     # bootstrap Package object
 | |
|     pkg = Package(pkgname, exportdefs, kw)
 | |
|     seen = { pkgname : pkg.module }
 | |
|     deferred_imports = []
 | |
| 
 | |
|     for pypath, extpy in pkg.exportitems():
 | |
|         pyparts = pypath.split('.')
 | |
|         modparts = pyparts[:]
 | |
|         if extpy[1] != '*': 
 | |
|             lastmodpart = modparts.pop()
 | |
|         else: 
 | |
|             lastmodpart = '*'
 | |
|         current = pkgname
 | |
| 
 | |
|         # ensure modules
 | |
|         for name in modparts:
 | |
|             previous = current
 | |
|             current += '.' + name
 | |
|             if current not in seen:
 | |
|                 seen[current] = mod = ApiModule(pkg, current)
 | |
|                 setattr(seen[previous], name, mod)
 | |
|                 setmodule(current, mod)
 | |
| 
 | |
|         mod = seen[current]
 | |
|         if not hasattr(mod, '__map__'):
 | |
|             assert mod is pkg.module, \
 | |
|                    "only root modules are allowed to be non-lazy. "
 | |
|             deferred_imports.append((mod, pyparts[-1], extpy))
 | |
|         else:
 | |
|             if extpy[1] == '__doc__':
 | |
|                 mod.__doc__ = pkg._resolve(extpy)
 | |
|             else:
 | |
|                 mod.__map__[lastmodpart] = extpy 
 | |
| 
 | |
|     for mod, pypart, extpy in deferred_imports: 
 | |
|         setattr(mod, pypart, pkg._resolve(extpy))
 | |
| 
 | |
|     autoimport(pkgname)
 | |
| 
 | |
| def autoimport(pkgname):
 | |
|     import py
 | |
|     ENVKEY = pkgname.upper() + "_AUTOIMPORT"
 | |
|     if ENVKEY in os.environ:
 | |
|         for impname in os.environ[ENVKEY].split(","):
 | |
|             __import__(impname) 
 |