Mercurial > hg-stable
changeset 33531:9cbbf9118c6c
demandimport: prefer loaded module over package attribute (issue5617)
In general, the attribute of the same name is overwritten by executing an
import statement.
import a.b
print(a.b.c) # 'c' of a/b/__init__.py
from a.b.c import d
print(a.b.c) # a/b/c.py
However, this appears not true for the scenario described in the test case,
and surprisingly, "from a.b.c import d" works even if "a.b.c" is not a module.
This patch works around the problem by taking the right module from sys.modules
if available.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Sun, 16 Jul 2017 17:38:39 +0900 |
parents | 05e3fa254b6b |
children | d645fdfb749f |
files | hgdemandimport/demandimportpy2.py tests/test-extension.t |
diffstat | 2 files changed, 29 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- a/hgdemandimport/demandimportpy2.py Sun Jul 16 17:19:22 2017 +0900 +++ b/hgdemandimport/demandimportpy2.py Sun Jul 16 17:38:39 2017 +0900 @@ -227,10 +227,14 @@ # recurse down the module chain, and return the leaf module mod = rootmod for comp in modname.split('.')[1:]: - if getattr(mod, comp, nothing) is nothing: - setattr(mod, comp, _demandmod(comp, mod.__dict__, - mod.__dict__, level=1)) - mod = getattr(mod, comp) + obj = getattr(mod, comp, nothing) + if obj is nothing: + obj = _demandmod(comp, mod.__dict__, mod.__dict__, level=1) + setattr(mod, comp, obj) + elif mod.__name__ + '.' + comp in sys.modules: + # prefer loaded module over attribute (issue5617) + obj = sys.modules[mod.__name__ + '.' + comp] + mod = obj return mod if level >= 0:
--- a/tests/test-extension.t Sun Jul 16 17:19:22 2017 +0900 +++ b/tests/test-extension.t Sun Jul 16 17:38:39 2017 +0900 @@ -337,6 +337,23 @@ > from .legacy import detail as legacydetail > EOF +Setup package that re-exports an attribute of its submodule as the same +name. This leaves 'shadowing.used' pointing to 'used.detail', but still +the submodule 'used' should be somehow accessible. (issue5617) + + $ mkdir -p $TESTTMP/extlibroot/shadowing + $ cat > $TESTTMP/extlibroot/shadowing/used.py <<EOF + > detail = "this is extlibroot.shadowing.used" + > EOF + $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<EOF + > from __future__ import absolute_import + > from extlibroot.shadowing.used import detail + > EOF + $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<EOF + > from __future__ import absolute_import + > from .used import detail as used + > EOF + Setup extension local modules to be imported with "absolute_import" feature. @@ -429,6 +446,7 @@ > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused > from extlibroot.lsub1.lsub2.called import func as lfunc > from extlibroot.recursedown import absdetail, legacydetail + > from extlibroot.shadowing import proxied > > def uisetup(ui): > result = [] @@ -436,6 +454,7 @@ > result.append(lfunc()) > result.append(absdetail) > result.append(legacydetail) + > result.append(proxied.detail) > ui.write('LIB: %s\n' % '\nLIB: '.join(result)) > EOF @@ -446,6 +465,7 @@ LIB: this is extlibroot.lsub1.lsub2.called.func() LIB: this is extlibroot.recursedown.abs.used LIB: this is extlibroot.recursedown.legacy.used + LIB: this is extlibroot.shadowing.used ABS: this is absextroot.xsub1.xsub2.used ABS: this is absextroot.xsub1.xsub2.called.func() @@ -454,6 +474,7 @@ LIB: this is extlibroot.lsub1.lsub2.called.func() LIB: this is extlibroot.recursedown.abs.used LIB: this is extlibroot.recursedown.legacy.used + LIB: this is extlibroot.shadowing.used REL: this is absextroot.xsub1.xsub2.used REL: this is absextroot.xsub1.xsub2.called.func() REL: this relimporter imports 'this is absextroot.relimportee'