demandimport: support "absolute_import" for external libraries (issue4029)
authorFUJIWARA Katsunori <foozy@lares.dti.ne.jp>
Sat, 05 Oct 2013 01:02:22 +0900
changeset 19932 e3a5922e18c3
parent 19931 8bbe208c1812
child 19933 621a26eb3a99
demandimport: support "absolute_import" for external libraries (issue4029) Before this patch, demandimport of Mercurial may fail to load external libraries using "from __future__ import absolute_import": for example, importing "foo" in "bar.baz" module will load "bar.foo" if it exists, even though "absolute_import" is enabled in "bar.baz" module. So, extensions for Mercurial can't use such external libraries. This patch saves "level" of import request for on-demand module loading in the future: default value of level is -1, and level is 0 when "absolute_import" is enabled. "level" value is passed to built-in import function in "_demandmod._load()" and it should load target module correctly. This patch changes only one "_demandmod" construction case other than cases below: - construction in "_demandmod._load()" this code path should be used only in relative sub-module loading case - constructions other than patched one in"_demandimport()" these code paths shouldn't be used in "level != -1" case
mercurial/demandimport.py
tests/test-extension.t
--- a/mercurial/demandimport.py	Sat Oct 05 01:02:22 2013 +0900
+++ b/mercurial/demandimport.py	Sat Oct 05 01:02:22 2013 +0900
@@ -40,22 +40,23 @@
 
 class _demandmod(object):
     """module demand-loader and proxy"""
-    def __init__(self, name, globals, locals):
+    def __init__(self, name, globals, locals, level=-1):
         if '.' in name:
             head, rest = name.split('.', 1)
             after = [rest]
         else:
             head = name
             after = []
-        object.__setattr__(self, "_data", (head, globals, locals, after))
+        object.__setattr__(self, "_data",
+                           (head, globals, locals, after, level))
         object.__setattr__(self, "_module", None)
     def _extend(self, name):
         """add to the list of submodules to load"""
         self._data[3].append(name)
     def _load(self):
         if not self._module:
-            head, globals, locals, after = self._data
-            mod = _origimport(head, globals, locals)
+            head, globals, locals, after, level = self._data
+            mod = _import(head, globals, locals, None, level)
             # load submodules
             def subload(mod, p):
                 h, t = p, None
@@ -105,7 +106,7 @@
                 if isinstance(locals[base], _demandmod):
                     locals[base]._extend(rest)
                 return locals[base]
-        return _demandmod(name, globals, locals)
+        return _demandmod(name, globals, locals, level)
     else:
         if level != -1:
             # from . import b,c,d or from .a import b,c,d
--- a/tests/test-extension.t	Sat Oct 05 01:02:22 2013 +0900
+++ b/tests/test-extension.t	Sat Oct 05 01:02:22 2013 +0900
@@ -129,6 +129,45 @@
   $ echo 'foo = !' >> $HGRCPATH
   $ echo 'bar = !' >> $HGRCPATH
 
+Check "from __future__ import absolute_import" support for external libraries
+
+  $ mkdir $TESTTMP/libroot
+  $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
+  $ mkdir $TESTTMP/libroot/mod
+  $ touch $TESTTMP/libroot/mod/__init__.py
+  $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
+
+#if absimport
+  $ cat > $TESTTMP/libroot/mod/ambigabs.py <<EOF
+  > from __future__ import absolute_import
+  > import ambig # should load "libroot/ambig.py"
+  > s = ambig.s
+  > EOF
+  $ cat > loadabs.py <<EOF
+  > import mod.ambigabs as ambigabs
+  > def extsetup():
+  >     print 'ambigabs.s=%s' % ambigabs.s
+  > EOF
+  $ (PYTHONPATH=$PYTHONPATH:$TESTTMP/libroot; hg --config extensions.loadabs=loadabs.py root)
+  ambigabs.s=libroot/ambig.py
+  $TESTTMP/a
+#endif
+
+#if no-py3k
+  $ cat > $TESTTMP/libroot/mod/ambigrel.py <<EOF
+  > import ambig # should load "libroot/mod/ambig.py"
+  > s = ambig.s
+  > EOF
+  $ cat > loadrel.py <<EOF
+  > import mod.ambigrel as ambigrel
+  > def extsetup():
+  >     print 'ambigrel.s=%s' % ambigrel.s
+  > EOF
+  $ (PYTHONPATH=$PYTHONPATH:$TESTTMP/libroot; hg --config extensions.loadrel=loadrel.py root)
+  ambigrel.s=libroot/mod/ambig.py
+  $TESTTMP/a
+#endif
+
   $ cd ..
 
 hide outer repo