From 2d5594fac21a81a06f82c3605318dfa96e72398f Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 9 Sep 2019 09:45:18 -0700 Subject: [PATCH] bpo-20490: Improve circular import error message (GH-15308) --- Lib/test/test_import/__init__.py | 10 ++++++++++ .../data/circular_imports/from_cycle1.py | 2 ++ .../data/circular_imports/from_cycle2.py | 2 ++ .../2019-08-15-12-48-36.bpo-20490.-hXeEn.rst | 2 ++ Python/ceval.c | 15 +++++++++++---- 5 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 Lib/test/test_import/data/circular_imports/from_cycle1.py create mode 100644 Lib/test/test_import/data/circular_imports/from_cycle2.py create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-08-15-12-48-36.bpo-20490.-hXeEn.rst diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 50406d9aa1d..7c24f0e5023 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1326,6 +1326,16 @@ class CircularImportTests(unittest.TestCase): self.assertIn('partially initialized module', errmsg) self.assertIn('circular import', errmsg) + def test_circular_from_import(self): + with self.assertRaises(ImportError) as cm: + import test.test_import.data.circular_imports.from_cycle1 + self.assertIn( + "cannot import name 'b' from partially initialized module " + "'test.test_import.data.circular_imports.from_cycle1' " + "(most likely due to a circular import)", + str(cm.exception), + ) + if __name__ == '__main__': # Test needs to be a package, so we can do relative imports. diff --git a/Lib/test/test_import/data/circular_imports/from_cycle1.py b/Lib/test/test_import/data/circular_imports/from_cycle1.py new file mode 100644 index 00000000000..aacfd5f46fc --- /dev/null +++ b/Lib/test/test_import/data/circular_imports/from_cycle1.py @@ -0,0 +1,2 @@ +from .from_cycle2 import a +b = 1 diff --git a/Lib/test/test_import/data/circular_imports/from_cycle2.py b/Lib/test/test_import/data/circular_imports/from_cycle2.py new file mode 100644 index 00000000000..62a66e1cfd0 --- /dev/null +++ b/Lib/test/test_import/data/circular_imports/from_cycle2.py @@ -0,0 +1,2 @@ +from .from_cycle1 import b +a = 1 diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-08-15-12-48-36.bpo-20490.-hXeEn.rst b/Misc/NEWS.d/next/Core and Builtins/2019-08-15-12-48-36.bpo-20490.-hXeEn.rst new file mode 100644 index 00000000000..dfee480e1aa --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-08-15-12-48-36.bpo-20490.-hXeEn.rst @@ -0,0 +1,2 @@ +Improve import error message for partially initialized module on circular +``from`` imports - by Anthony Sottile. diff --git a/Python/ceval.c b/Python/ceval.c index 546a4264d8a..07ec3293adf 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -5236,10 +5236,17 @@ import_from(PyThreadState *tstate, PyObject *v, PyObject *name) PyErr_SetImportError(errmsg, pkgname, NULL); } else { - errmsg = PyUnicode_FromFormat( - "cannot import name %R from %R (%S)", - name, pkgname_or_unknown, pkgpath - ); + _Py_IDENTIFIER(__spec__); + PyObject *spec = _PyObject_GetAttrId(v, &PyId___spec__); + Py_XINCREF(spec); + const char *fmt = + _PyModuleSpec_IsInitializing(spec) ? + "cannot import name %R from partially initialized module %R " + "(most likely due to a circular import) (%S)" : + "cannot import name %R from %R (%S)"; + Py_XDECREF(spec); + + errmsg = PyUnicode_FromFormat(fmt, name, pkgname_or_unknown, pkgpath); /* NULL checks for errmsg and pkgname done by PyErr_SetImportError. */ PyErr_SetImportError(errmsg, pkgname, pkgpath); }