Skip to content

Commit 0e7e1f9

Browse files
merge mainline
1 parent 0b9f4ab commit 0e7e1f9

File tree

5 files changed

+184
-20
lines changed

5 files changed

+184
-20
lines changed

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
1.2.dev
2+
----------------------------------------
3+
4+
- Allow to import from Aliasmodules (thanks Ralf Schmitt)
5+
- avoid loading __doc__ early, so ApiModule is now fully lazy
6+
17
1.1
28
----------------------------------------
39

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
Permission is hereby granted, free of charge, to any person obtaining a copy
3+
of this software and associated documentation files (the "Software"), to deal
4+
in the Software without restriction, including without limitation the rights
5+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6+
copies of the Software, and to permit persons to whom the Software is
7+
furnished to do so, subject to the following conditions:
8+
9+
The above copyright notice and this permission notice shall be included in all
10+
copies or substantial portions of the Software.
11+
12+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18+
SOFTWARE.
19+

apipkg.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
import sys
1010
from types import ModuleType
1111

12-
__version__ = "1.1"
12+
__version__ = '1.2.dev7'
1313

1414
def initpkg(pkgname, exportdefs, attr=dict()):
1515
""" initialize given package from the export definitions. """
16-
oldmod = sys.modules[pkgname]
16+
oldmod = sys.modules.get(pkgname)
1717
d = {}
1818
f = getattr(oldmod, '__file__', None)
1919
if f:
@@ -25,10 +25,11 @@ def initpkg(pkgname, exportdefs, attr=dict()):
2525
d['__loader__'] = oldmod.__loader__
2626
if hasattr(oldmod, '__path__'):
2727
d['__path__'] = [os.path.abspath(p) for p in oldmod.__path__]
28-
if hasattr(oldmod, '__doc__'):
28+
if '__doc__' not in exportdefs and getattr(oldmod, '__doc__', None):
2929
d['__doc__'] = oldmod.__doc__
3030
d.update(attr)
31-
oldmod.__dict__.update(d)
31+
if hasattr(oldmod, "__dict__"):
32+
oldmod.__dict__.update(d)
3233
mod = ApiModule(pkgname, exportdefs, implprefix=pkgname, attr=d)
3334
sys.modules[pkgname] = mod
3435

@@ -44,6 +45,16 @@ def importobj(modpath, attrname):
4445
return retval
4546

4647
class ApiModule(ModuleType):
48+
def __docget(self):
49+
try:
50+
return self.__doc
51+
except AttributeError:
52+
if '__doc__' in self.__map__:
53+
return self.__makeattr('__doc__')
54+
def __docset(self, value):
55+
self.__doc = value
56+
__doc__ = property(__docget, __docset)
57+
4758
def __init__(self, name, importspec, implprefix=None, attr=None):
4859
self.__name__ = name
4960
self.__all__ = [x for x in importspec if x != '__onfirstaccess__']
@@ -65,8 +76,13 @@ def __init__(self, name, importspec, implprefix=None, attr=None):
6576
attrname = parts and parts[0] or ""
6677
if modpath[0] == '.':
6778
modpath = implprefix + modpath
68-
if name == '__doc__':
69-
self.__doc__ = importobj(modpath, attrname)
79+
80+
if not attrname:
81+
subname = '%s.%s'%(self.__name__, name)
82+
apimod = AliasModule(subname, modpath)
83+
sys.modules[subname] = apimod
84+
if '.' not in name:
85+
setattr(self, name, apimod)
7086
else:
7187
self.__map__[name] = (modpath, attrname)
7288

@@ -118,3 +134,34 @@ def __dict__(self):
118134
pass
119135
return dict
120136
__dict__ = property(__dict__)
137+
138+
139+
def AliasModule(modname, modpath, attrname=None):
140+
mod = []
141+
142+
def getmod():
143+
if not mod:
144+
x = importobj(modpath, None)
145+
if attrname is not None:
146+
x = getattr(x, attrname)
147+
mod.append(x)
148+
return mod[0]
149+
150+
class AliasModule(ModuleType):
151+
152+
def __repr__(self):
153+
x = modpath
154+
if attrname:
155+
x += "." + attrname
156+
return '<AliasModule %r for %r>' % (modname, x)
157+
158+
def __getattribute__(self, name):
159+
return getattr(getmod(), name)
160+
161+
def __setattr__(self, name, value):
162+
setattr(getmod(), name, value)
163+
164+
def __delattr__(self, name):
165+
delattr(getmod(), name)
166+
167+
return AliasModule(modname)

setup.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
except ImportError:
1414
from distutils.core import setup
1515

16-
from apipkg import __version__
17-
1816
def main():
1917
setup(
2018
name='apipkg',
2119
description=
2220
'apipkg: namespace control and lazy-import mechanism',
2321
long_description = open('README.txt').read(),
24-
version= __version__,
22+
version='1.2.dev7',
2523
url='http://bitbucket.org/hpk42/apipkg',
2624
license='MIT License',
2725
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

test_apipkg.py

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,12 @@ def test_recursive_import(self, monkeypatch, tmpdir):
115115
})
116116
"""))
117117
pkgdir.join('submod.py').write(py.code.Source("""
118-
import recmodule
118+
import recmodule
119119
class someclass: pass
120120
print (recmodule.__dict__)
121121
"""))
122122
monkeypatch.syspath_prepend(tmpdir)
123-
import recmodule
123+
import recmodule
124124
assert isinstance(recmodule, apipkg.ApiModule)
125125
assert recmodule.some.__name__ == "someclass"
126126

@@ -134,7 +134,20 @@ def test_module_alias_import(self, monkeypatch, tmpdir):
134134
"""))
135135
monkeypatch.syspath_prepend(tmpdir)
136136
import aliasimport
137-
assert aliasimport.some is py.std.os.path
137+
for k, v in py.std.os.path.__dict__.items():
138+
assert getattr(aliasimport.some, k) == v
139+
140+
def test_from_module_alias_import(self, monkeypatch, tmpdir):
141+
pkgdir = tmpdir.mkdir("fromaliasimport")
142+
pkgdir.join('__init__.py').write(py.code.Source("""
143+
import apipkg
144+
apipkg.initpkg(__name__, exportdefs={
145+
'some': 'os.path',
146+
})
147+
"""))
148+
monkeypatch.syspath_prepend(tmpdir)
149+
from fromaliasimport.some import join
150+
assert join is py.std.os.path.join
138151

139152
def xtest_nested_absolute_imports():
140153
import email
@@ -211,14 +224,22 @@ def test_initpkg_transfers_attrs(monkeypatch):
211224
assert newmod.__loader__ == mod.__loader__
212225
assert newmod.__doc__ == mod.__doc__
213226

214-
def test_initpkg_not_overwrite_exportdefs(monkeypatch):
227+
def test_initpkg_nodoc(monkeypatch):
215228
mod = type(sys)('hello')
216-
mod.__doc__ = "this is the documentation"
229+
mod.__file__ = "hello.py"
217230
monkeypatch.setitem(sys.modules, 'hello', mod)
218-
apipkg.initpkg('hello', {"__doc__": "sys:__doc__"})
231+
apipkg.initpkg('hello', {})
219232
newmod = sys.modules['hello']
220-
assert newmod != mod
221-
assert newmod.__doc__ == sys.__doc__
233+
assert not newmod.__doc__
234+
235+
def test_initpkg_overwrite_doc(monkeypatch):
236+
hello = type(sys)('hello')
237+
hello.__doc__ = "this is the documentation"
238+
monkeypatch.setitem(sys.modules, 'hello', hello)
239+
apipkg.initpkg('hello', {"__doc__": "sys:__doc__"})
240+
newhello = sys.modules['hello']
241+
assert newhello != hello
242+
assert newhello.__doc__ == sys.__doc__
222243

223244
def test_initpkg_not_transfers_not_existing_attrs(monkeypatch):
224245
mod = type(sys)('hello')
@@ -276,7 +297,7 @@ def test_onfirstaccess(tmpdir, monkeypatch):
276297
"""))
277298
pkgdir.join('submod.py').write(py.code.Source("""
278299
l = []
279-
def init():
300+
def init():
280301
l.append(1)
281302
"""))
282303
monkeypatch.syspath_prepend(tmpdir)
@@ -298,9 +319,9 @@ def test_onfirstaccess_setsnewattr(tmpdir, monkeypatch, mode):
298319
)
299320
"""))
300321
pkgdir.join('submod.py').write(py.code.Source("""
301-
def init():
322+
def init():
302323
import %s as pkg
303-
pkg.newattr = 42
324+
pkg.newattr = 42
304325
""" % pkgname))
305326
monkeypatch.syspath_prepend(tmpdir)
306327
mod = __import__(pkgname)
@@ -380,3 +401,76 @@ def test_extra_attributes(tmpdir, monkeypatch):
380401
monkeypatch.syspath_prepend(tmpdir)
381402
import extra_attributes
382403
assert extra_attributes.foo == 'bar'
404+
405+
def test_aliasmodule_aliases_an_attribute():
406+
am = apipkg.AliasModule("mymod", "pprint", 'PrettyPrinter')
407+
r = repr(am)
408+
assert "<AliasModule 'mymod' for 'pprint.PrettyPrinter'>" == r
409+
assert am.format
410+
411+
def test_aliasmodule_repr():
412+
am = apipkg.AliasModule("mymod", "sys")
413+
r = repr(am)
414+
assert "<AliasModule 'mymod' for 'sys'>" == r
415+
am.version
416+
assert repr(am) == r
417+
418+
def test_aliasmodule_proxy_methods(tmpdir, monkeypatch):
419+
pkgdir = tmpdir
420+
pkgdir.join('aliasmodule_proxy.py').write(py.code.Source("""
421+
def doit():
422+
return 42
423+
"""))
424+
425+
pkgdir.join('my_aliasmodule_proxy.py').write(py.code.Source("""
426+
import apipkg
427+
apipkg.initpkg(__name__, dict(proxy='aliasmodule_proxy'))
428+
429+
def doit():
430+
return 42
431+
"""))
432+
433+
monkeypatch.syspath_prepend(tmpdir)
434+
import aliasmodule_proxy as orig
435+
from my_aliasmodule_proxy import proxy
436+
437+
doit = proxy.doit
438+
assert doit is orig.doit
439+
440+
del proxy.doit
441+
py.test.raises(AttributeError, "orig.doit")
442+
443+
proxy.doit = doit
444+
assert orig.doit is doit
445+
446+
def test_aliasmodule_nested_import_with_from(tmpdir, monkeypatch):
447+
import os
448+
pkgdir = tmpdir.mkdir("api1")
449+
pkgdir.ensure("__init__.py").write(py.std.textwrap.dedent("""
450+
import apipkg
451+
apipkg.initpkg(__name__, {
452+
'os2': 'api2',
453+
'os2.path': 'api2.path2',
454+
})
455+
"""))
456+
tmpdir.join("api2.py").write(py.std.textwrap.dedent("""
457+
import os, sys
458+
from os import path
459+
sys.modules['api2.path2'] = path
460+
x = 3
461+
"""))
462+
monkeypatch.syspath_prepend(tmpdir)
463+
from api1 import os2
464+
from api1.os2.path import abspath
465+
assert abspath == os.path.abspath
466+
# check that api1.os2 mirrors os.*
467+
assert os2.x == 3
468+
import api1
469+
assert 'os2.path' not in api1.__dict__
470+
471+
472+
def test_initpkg_without_old_module():
473+
apipkg.initpkg("initpkg_without_old_module",
474+
dict(modules="sys:modules"))
475+
from initpkg_without_old_module import modules
476+
assert modules is sys.modules

0 commit comments

Comments
 (0)