Patchwork [3,of,4,V4] demandimport: support "absolute_import" for external libraries (issue4029)

login
register
mail settings
Submitter Katsunori FUJIWARA
Date Oct. 4, 2013, 4:19 p.m.
Message ID <9b9a9dccb19cf7eef9a3.1380903547@juju>
Download mbox | patch
Permalink /patch/2732/
State Accepted
Commit e3a5922e18c3bfe914a9a100546cc2c631339409
Headers show

Comments

Katsunori FUJIWARA - Oct. 4, 2013, 4:19 p.m.
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1380902542 -32400
#      Sat Oct 05 01:02:22 2013 +0900
# Node ID 9b9a9dccb19cf7eef9a39cbd505053df9a4aeab7
# Parent  8fb6dc24cd54553cfafe43d6c6d4b6c598340da5
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

Patch

diff --git a/mercurial/demandimport.py b/mercurial/demandimport.py
--- a/mercurial/demandimport.py
+++ b/mercurial/demandimport.py
@@ -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
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -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