test_ok1/py/initpkg.py

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)