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