diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index d61782a6cb9..df678f17f18 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -80,6 +80,25 @@ class ImportTests(unittest.TestCase): with self.assertRaises(ImportError): from importlib import something_that_should_not_exist_anywhere + def test_from_import_missing_attr_has_name_and_path(self): + with self.assertRaises(ImportError) as cm: + from os import i_dont_exist + self.assertEqual(cm.exception.name, 'os') + self.assertEqual(cm.exception.path, os.__file__) + + def test_from_import_missing_attr_has_name(self): + with self.assertRaises(ImportError) as cm: + # _warning has no path as it's a built-in module. + from _warning import i_dont_exist + self.assertEqual(cm.exception.name, '_warning') + self.assertIsNone(cm.exception.path) + + def test_from_import_missing_attr_path_is_canonical(self): + with self.assertRaises(ImportError) as cm: + from os.path import i_dont_exist + self.assertIn(cm.exception.name, {'posixpath', 'ntpath'}) + self.assertIsNotNone(cm.exception) + def test_case_sensitivity(self): # Brief digression to test that import is case-sensitive: if we got # this far, we know for sure that "random" exists. diff --git a/Misc/NEWS b/Misc/NEWS index e024e01a300..51055ef6f6f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,8 @@ Core and Builtins - bpo-29438: Fixed use-after-free problem in key sharing dict. +- bpo-29546: Set the 'path' and 'name' attribute on ImportError for ``from ... import ...``. + - Issue #29319: Prevent RunMainFromImporter overwriting sys.path[0]. - Issue #29337: Fixed possible BytesWarning when compare the code objects. diff --git a/Python/ceval.c b/Python/ceval.c index 66fd3615026..69c93838419 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4995,7 +4995,7 @@ import_from(PyObject *v, PyObject *name) { PyObject *x; _Py_IDENTIFIER(__name__); - PyObject *fullmodname, *pkgname; + PyObject *fullmodname, *pkgname, *pkgpath; x = PyObject_GetAttr(v, name); if (x != NULL || !PyErr_ExceptionMatches(PyExc_AttributeError)) @@ -5021,7 +5021,15 @@ import_from(PyObject *v, PyObject *name) Py_INCREF(x); return x; error: - PyErr_Format(PyExc_ImportError, "cannot import name %R", name); + pkgpath = PyModule_GetFilenameObject(v); + + if (pkgpath == NULL || !PyUnicode_Check(pkgpath)) { + PyErr_Clear(); + PyErr_SetImportError(PyUnicode_FromFormat("cannot import name %R", name), pkgname, NULL); + } else { + PyErr_SetImportError(PyUnicode_FromFormat("cannot import name %R", name), pkgname, pkgpath); + } + return NULL; }