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.
--- 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'