[Python-checkins] cpython: Issue #17636: Circular imports involving relative imports are now supported.

antoine.pitrou python-checkins at python.org
Mon Oct 13 20:21:32 CEST 2014


https://hg.python.org/cpython/rev/fded07a2d616
changeset:   93026:fded07a2d616
parent:      93023:c0224ff67cdd
user:        Antoine Pitrou <solipsis at pitrou.net>
date:        Mon Oct 13 20:19:45 2014 +0200
summary:
  Issue #17636: Circular imports involving relative imports are now supported.

files:
  Lib/test/test_import.py                                          |  42 +++++++++-
  Lib/test/test_import/__main__.py                                 |   3 +
  Lib/test/test_import/data/circular_imports/basic.py              |   2 +
  Lib/test/test_import/data/circular_imports/basic2.py             |   1 +
  Lib/test/test_import/data/circular_imports/indirect.py           |   1 +
  Lib/test/test_import/data/circular_imports/rebinding.py          |   3 +
  Lib/test/test_import/data/circular_imports/rebinding2.py         |   3 +
  Lib/test/test_import/data/circular_imports/subpackage.py         |   2 +
  Lib/test/test_import/data/circular_imports/subpkg/subpackage2.py |   2 +
  Lib/test/test_import/data/circular_imports/subpkg/util.py        |   2 +
  Lib/test/test_import/data/circular_imports/util.py               |   2 +
  Misc/NEWS                                                        |   3 +
  Python/ceval.c                                                   |  22 ++++-
  13 files changed, 85 insertions(+), 3 deletions(-)


diff --git a/Lib/test/test_import.py b/Lib/test/test_import/__init__.py
rename from Lib/test/test_import.py
rename to Lib/test/test_import/__init__.py
--- a/Lib/test/test_import.py
+++ b/Lib/test/test_import/__init__.py
@@ -568,7 +568,7 @@
 
     def test_relimport_star(self):
         # This will import * from .test_import.
-        from . import relimport
+        from .. import relimport
         self.assertTrue(hasattr(relimport, "RelativeImportTests"))
 
     def test_issue3221(self):
@@ -1068,6 +1068,46 @@
                                        __isolated=False)
 
 
+class CircularImportTests(unittest.TestCase):
+
+    """See the docstrings of the modules being imported for the purpose of the
+    test."""
+
+    def tearDown(self):
+        """Make sure no modules pre-exist in sys.modules which are being used to
+        test."""
+        for key in list(sys.modules.keys()):
+            if key.startswith('test.test_import.data.circular_imports'):
+                del sys.modules[key]
+
+    def test_direct(self):
+        try:
+            import test.test_import.data.circular_imports.basic
+        except ImportError:
+            self.fail('circular import through relative imports failed')
+
+    def test_indirect(self):
+        try:
+            import test.test_import.data.circular_imports.indirect
+        except ImportError:
+            self.fail('relative import in module contributing to circular '
+                      'import failed')
+
+    def test_subpackage(self):
+        try:
+            import test.test_import.data.circular_imports.subpackage
+        except ImportError:
+            self.fail('circular import involving a subpackage failed')
+
+    def test_rebinding(self):
+        try:
+            import test.test_import.data.circular_imports.rebinding as rebinding
+        except ImportError:
+            self.fail('circular import with rebinding of module attribute failed')
+        from test.test_import.data.circular_imports.subpkg import util
+        self.assertIs(util.util, rebinding.util)
+
+
 if __name__ == '__main__':
     # Test needs to be a package, so we can do relative imports.
     unittest.main()
diff --git a/Lib/test/test_import/__main__.py b/Lib/test/test_import/__main__.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/__main__.py
@@ -0,0 +1,3 @@
+import unittest
+
+unittest.main('test.test_import')
\ No newline at end of file
diff --git a/Lib/test/test_import/data/circular_imports/basic.py b/Lib/test/test_import/data/circular_imports/basic.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/basic.py
@@ -0,0 +1,2 @@
+"""Circular imports through direct, relative imports."""
+from . import basic2
diff --git a/Lib/test/test_import/data/circular_imports/basic2.py b/Lib/test/test_import/data/circular_imports/basic2.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/basic2.py
@@ -0,0 +1,1 @@
+from . import basic
diff --git a/Lib/test/test_import/data/circular_imports/indirect.py b/Lib/test/test_import/data/circular_imports/indirect.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/indirect.py
@@ -0,0 +1,1 @@
+from . import basic, basic2
diff --git a/Lib/test/test_import/data/circular_imports/rebinding.py b/Lib/test/test_import/data/circular_imports/rebinding.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/rebinding.py
@@ -0,0 +1,3 @@
+"""Test the binding of names when a circular import shares the same name as an
+attribute."""
+from .rebinding2 import util
\ No newline at end of file
diff --git a/Lib/test/test_import/data/circular_imports/rebinding2.py b/Lib/test/test_import/data/circular_imports/rebinding2.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/rebinding2.py
@@ -0,0 +1,3 @@
+from .subpkg import util
+from . import rebinding
+util = util.util
\ No newline at end of file
diff --git a/Lib/test/test_import/data/circular_imports/subpackage.py b/Lib/test/test_import/data/circular_imports/subpackage.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/subpackage.py
@@ -0,0 +1,2 @@
+"""Circular import involving a sub-package."""
+from .subpkg import subpackage2
\ No newline at end of file
diff --git a/Lib/test/test_import/data/circular_imports/subpkg/subpackage2.py b/Lib/test/test_import/data/circular_imports/subpkg/subpackage2.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/subpkg/subpackage2.py
@@ -0,0 +1,2 @@
+#from .util import util
+from .. import subpackage
\ No newline at end of file
diff --git a/Lib/test/test_import/data/circular_imports/subpkg/util.py b/Lib/test/test_import/data/circular_imports/subpkg/util.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/subpkg/util.py
@@ -0,0 +1,2 @@
+def util():
+    pass
\ No newline at end of file
diff --git a/Lib/test/test_import/data/circular_imports/util.py b/Lib/test/test_import/data/circular_imports/util.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/test_import/data/circular_imports/util.py
@@ -0,0 +1,2 @@
+def util():
+    pass
\ No newline at end of file
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 Core and Builtins
 -----------------
 
+- Issue #17636: Circular imports involving relative imports are now
+  supported.
+
 - Issue #22604: Fix assertion error in debug mode when dividing a complex
   number by (nan+0j).
 
diff --git a/Python/ceval.c b/Python/ceval.c
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -4693,11 +4693,29 @@
 import_from(PyObject *v, PyObject *name)
 {
     PyObject *x;
+    _Py_IDENTIFIER(__name__);
+    PyObject *fullmodname, *pkgname;
 
     x = PyObject_GetAttr(v, name);
-    if (x == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
+    if (x != NULL || !PyErr_ExceptionMatches(PyExc_AttributeError))
+        return x;
+    /* Issue #17636: in case this failed because of a circular relative
+       import, try to fallback on reading the module directly from
+       sys.modules. */
+    PyErr_Clear();
+    pkgname = _PyObject_GetAttrId(v, &PyId___name__);
+    if (pkgname == NULL)
+        return NULL;
+    fullmodname = PyUnicode_FromFormat("%U.%U", pkgname, name);
+    Py_DECREF(pkgname);
+    if (fullmodname == NULL)
+        return NULL;
+    x = PyDict_GetItem(PyImport_GetModuleDict(), fullmodname);
+    if (x == NULL)
         PyErr_Format(PyExc_ImportError, "cannot import name %R", name);
-    }
+    else
+        Py_INCREF(x);
+    Py_DECREF(fullmodname);
     return x;
 }
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list