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