You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
6.5KB

  1. """
  2. apipkg: control the exported namespace of a Python package.
  3. see https://pypi.python.org/pypi/apipkg
  4. (c) holger krekel, 2009 - MIT license
  5. """
  6. import os
  7. import sys
  8. from types import ModuleType
  9. from .version import version as __version__
  10. def _py_abspath(path):
  11. """
  12. special version of abspath
  13. that will leave paths from jython jars alone
  14. """
  15. if path.startswith('__pyclasspath__'):
  16. return path
  17. else:
  18. return os.path.abspath(path)
  19. def distribution_version(name):
  20. """try to get the version of the named distribution,
  21. returs None on failure"""
  22. from pkg_resources import get_distribution, DistributionNotFound
  23. try:
  24. dist = get_distribution(name)
  25. except DistributionNotFound:
  26. pass
  27. else:
  28. return dist.version
  29. def initpkg(pkgname, exportdefs, attr=None, eager=False):
  30. """ initialize given package from the export definitions. """
  31. attr = attr or {}
  32. oldmod = sys.modules.get(pkgname)
  33. d = {}
  34. f = getattr(oldmod, '__file__', None)
  35. if f:
  36. f = _py_abspath(f)
  37. d['__file__'] = f
  38. if hasattr(oldmod, '__version__'):
  39. d['__version__'] = oldmod.__version__
  40. if hasattr(oldmod, '__loader__'):
  41. d['__loader__'] = oldmod.__loader__
  42. if hasattr(oldmod, '__path__'):
  43. d['__path__'] = [_py_abspath(p) for p in oldmod.__path__]
  44. if hasattr(oldmod, '__package__'):
  45. d['__package__'] = oldmod.__package__
  46. if '__doc__' not in exportdefs and getattr(oldmod, '__doc__', None):
  47. d['__doc__'] = oldmod.__doc__
  48. d.update(attr)
  49. if hasattr(oldmod, "__dict__"):
  50. oldmod.__dict__.update(d)
  51. mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d)
  52. sys.modules[pkgname] = mod
  53. # eagerload in bypthon to avoid their monkeypatching breaking packages
  54. if 'bpython' in sys.modules or eager:
  55. for module in list(sys.modules.values()):
  56. if isinstance(module, ApiModule):
  57. module.__dict__
  58. def importobj(modpath, attrname):
  59. """imports a module, then resolves the attrname on it"""
  60. module = __import__(modpath, None, None, ['__doc__'])
  61. if not attrname:
  62. return module
  63. retval = module
  64. names = attrname.split(".")
  65. for x in names:
  66. retval = getattr(retval, x)
  67. return retval
  68. class ApiModule(ModuleType):
  69. """the magical lazy-loading module standing"""
  70. def __docget(self):
  71. try:
  72. return self.__doc
  73. except AttributeError:
  74. if '__doc__' in self.__map__:
  75. return self.__makeattr('__doc__')
  76. def __docset(self, value):
  77. self.__doc = value
  78. __doc__ = property(__docget, __docset)
  79. def __init__(self, name, importspec, implprefix=None, attr=None):
  80. self.__name__ = name
  81. self.__all__ = [x for x in importspec if x != '__onfirstaccess__']
  82. self.__map__ = {}
  83. self.__implprefix__ = implprefix or name
  84. if attr:
  85. for name, val in attr.items():
  86. # print "setting", self.__name__, name, val
  87. setattr(self, name, val)
  88. for name, importspec in importspec.items():
  89. if isinstance(importspec, dict):
  90. subname = '%s.%s' % (self.__name__, name)
  91. apimod = ApiModule(subname, importspec, implprefix)
  92. sys.modules[subname] = apimod
  93. setattr(self, name, apimod)
  94. else:
  95. parts = importspec.split(':')
  96. modpath = parts.pop(0)
  97. attrname = parts and parts[0] or ""
  98. if modpath[0] == '.':
  99. modpath = implprefix + modpath
  100. if not attrname:
  101. subname = '%s.%s' % (self.__name__, name)
  102. apimod = AliasModule(subname, modpath)
  103. sys.modules[subname] = apimod
  104. if '.' not in name:
  105. setattr(self, name, apimod)
  106. else:
  107. self.__map__[name] = (modpath, attrname)
  108. def __repr__(self):
  109. repr_list = []
  110. if hasattr(self, '__version__'):
  111. repr_list.append("version=" + repr(self.__version__))
  112. if hasattr(self, '__file__'):
  113. repr_list.append('from ' + repr(self.__file__))
  114. if repr_list:
  115. return '<ApiModule %r %s>' % (self.__name__, " ".join(repr_list))
  116. return '<ApiModule %r>' % (self.__name__,)
  117. def __makeattr(self, name):
  118. """lazily compute value for name or raise AttributeError if unknown."""
  119. # print "makeattr", self.__name__, name
  120. target = None
  121. if '__onfirstaccess__' in self.__map__:
  122. target = self.__map__.pop('__onfirstaccess__')
  123. importobj(*target)()
  124. try:
  125. modpath, attrname = self.__map__[name]
  126. except KeyError:
  127. if target is not None and name != '__onfirstaccess__':
  128. # retry, onfirstaccess might have set attrs
  129. return getattr(self, name)
  130. raise AttributeError(name)
  131. else:
  132. result = importobj(modpath, attrname)
  133. setattr(self, name, result)
  134. try:
  135. del self.__map__[name]
  136. except KeyError:
  137. pass # in a recursive-import situation a double-del can happen
  138. return result
  139. __getattr__ = __makeattr
  140. @property
  141. def __dict__(self):
  142. # force all the content of the module
  143. # to be loaded when __dict__ is read
  144. dictdescr = ModuleType.__dict__['__dict__']
  145. dict = dictdescr.__get__(self)
  146. if dict is not None:
  147. hasattr(self, 'some')
  148. for name in self.__all__:
  149. try:
  150. self.__makeattr(name)
  151. except AttributeError:
  152. pass
  153. return dict
  154. def AliasModule(modname, modpath, attrname=None):
  155. mod = []
  156. def getmod():
  157. if not mod:
  158. x = importobj(modpath, None)
  159. if attrname is not None:
  160. x = getattr(x, attrname)
  161. mod.append(x)
  162. return mod[0]
  163. class AliasModule(ModuleType):
  164. def __repr__(self):
  165. x = modpath
  166. if attrname:
  167. x += "." + attrname
  168. return '<AliasModule %r for %r>' % (modname, x)
  169. def __getattribute__(self, name):
  170. try:
  171. return getattr(getmod(), name)
  172. except ImportError:
  173. return None
  174. def __setattr__(self, name, value):
  175. setattr(getmod(), name, value)
  176. def __delattr__(self, name):
  177. delattr(getmod(), name)
  178. return AliasModule(str(modname))