diff options
-rw-r--r-- | Lib/test/test_import/__init__.py (renamed from Lib/test/test_import.py) | 42 | ||||
-rw-r--r-- | Lib/test/test_import/__main__.py | 3 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/basic.py | 2 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/basic2.py | 1 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/indirect.py | 1 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/rebinding.py | 3 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/rebinding2.py | 3 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/subpackage.py | 2 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/subpkg/subpackage2.py | 2 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/subpkg/util.py | 2 | ||||
-rw-r--r-- | Lib/test/test_import/data/circular_imports/util.py | 2 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | 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 index b4842c5..fd21fa2 100644 --- a/Lib/test/test_import.py +++ b/Lib/test/test_import/__init__.py @@ -568,7 +568,7 @@ class RelativeImportTests(unittest.TestCase): 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 @@ class ImportTracebackTests(unittest.TestCase): __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 index 0000000..ce26cbd --- /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 index 0000000..3e41e39 --- /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 index 0000000..00bd2f2 --- /dev/null +++ b/Lib/test/test_import/data/circular_imports/basic2.py @@ -0,0 +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 index 0000000..6925788 --- /dev/null +++ b/Lib/test/test_import/data/circular_imports/indirect.py @@ -0,0 +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 index 0000000..f51d1c6 --- /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 index 0000000..62ca726 --- /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 index 0000000..cc36c3c --- /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 index 0000000..07e0bc0 --- /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 index 0000000..f1505e9 --- /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 index 0000000..f1505e9 --- /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 @@ -10,6 +10,9 @@ Release date: TBA 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 index 2dbf591..4b1d6ca 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4693,11 +4693,29 @@ static PyObject * 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; } |