284 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			9.4 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 _iterfiles(self):
 | 
						|
        from py.__.path.common import checker
 | 
						|
        base = self.getpath()
 | 
						|
        for x in base.visit(checker(file=1, notext='.pyc'),
 | 
						|
                            rec=checker(dotfile=0)):
 | 
						|
            yield x
 | 
						|
 | 
						|
    def shahexdigest(self, cache=[]):
 | 
						|
        """ return sha hexdigest for files contained in package. """
 | 
						|
        if cache:
 | 
						|
            return cache[0]
 | 
						|
        from sha import sha
 | 
						|
        sum = sha()
 | 
						|
        # XXX the checksum depends on the order in which visit() enumerates
 | 
						|
        # the files, and it doesn't depend on the file names and paths
 | 
						|
        for x in self._iterfiles():
 | 
						|
            sum.update(x.read())
 | 
						|
        cache.append(sum.hexdigest())
 | 
						|
        return cache[0]
 | 
						|
 | 
						|
    def getzipdata(self):
 | 
						|
        """ return string representing a zipfile containing the package. """
 | 
						|
        import zipfile
 | 
						|
        import py
 | 
						|
        try:
 | 
						|
            from cStringIO import StringIO
 | 
						|
        except ImportError:
 | 
						|
            from StringIO import StringIO
 | 
						|
        base = py.__pkg__.getpath().dirpath()
 | 
						|
        outf = StringIO()
 | 
						|
        f = zipfile.ZipFile(outf, 'w', compression=zipfile.ZIP_DEFLATED)
 | 
						|
        try:
 | 
						|
            for x in self._iterfiles():
 | 
						|
                f.write(str(x), x.relto(base))
 | 
						|
        finally:
 | 
						|
            f.close()
 | 
						|
        return outf.getvalue()
 | 
						|
 | 
						|
    def getrev(self):
 | 
						|
        import py
 | 
						|
        p = py.path.svnwc(self.module.__file__).dirpath()
 | 
						|
        try:
 | 
						|
            return p.info().rev
 | 
						|
        except (KeyboardInterrupt, MemoryError, SystemExit):
 | 
						|
            raise
 | 
						|
        except:
 | 
						|
            return 'unknown'
 | 
						|
 | 
						|
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 self.__map__.keys():
 | 
						|
                    hasattr(self, name)   # force attribute to be loaded, 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) 
 |