From 1bc156430bad8177b5beecf57979628c1d071230 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 22 Feb 2017 07:06:50 -0800 Subject: bpo-29546: Improve from-import error message with location (#103) bpo-29546: Improve from-import error message with location --- Doc/whatsnew/3.7.rst | 3 +++ Lib/test/test_import/__init__.py | 14 +++++++++++++- Misc/NEWS | 2 ++ Python/ceval.c | 25 +++++++++++++++++++++---- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 21621c5..f6367fd 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -83,6 +83,9 @@ Other Language Changes whitespace, not only spaces. (Contributed by Robert Xiao in :issue:`28927`.) +* :exc:`ImportError` now displays module name and module ``__file__`` path when + ``from ... import ...`` fails. :issue:`29546`. + New Modules =========== diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index df678f1..ace1a46 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -85,6 +85,15 @@ class ImportTests(unittest.TestCase): from os import i_dont_exist self.assertEqual(cm.exception.name, 'os') self.assertEqual(cm.exception.path, os.__file__) + self.assertRegex(str(cm.exception), "cannot import name 'i_dont_exist' from 'os' \(.*/Lib/os.py\)") + + def test_from_import_missing_attr_has_name_and_so_path(self): + import _opcode + with self.assertRaises(ImportError) as cm: + from _opcode import i_dont_exist + self.assertEqual(cm.exception.name, '_opcode') + self.assertEqual(cm.exception.path, _opcode.__file__) + self.assertRegex(str(cm.exception), "cannot import name 'i_dont_exist' from '_opcode' \(.*\.(so|dll)\)") def test_from_import_missing_attr_has_name(self): with self.assertRaises(ImportError) as cm: @@ -365,9 +374,12 @@ class ImportTests(unittest.TestCase): module_name = 'test_from_import_AttributeError' self.addCleanup(unload, module_name) sys.modules[module_name] = AlwaysAttributeError() - with self.assertRaises(ImportError): + with self.assertRaises(ImportError) as cm: from test_from_import_AttributeError import does_not_exist + self.assertEqual(str(cm.exception), + "cannot import name 'does_not_exist' from '' (unknown location)") + @skip_if_dont_write_bytecode class FilePermissionTests(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS index 57e5ab9..3fe9182 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -24,6 +24,8 @@ Core and Builtins - bpo-29546: Set the 'path' and 'name' attribute on ImportError for ``from ... import ...``. +- bpo-29546: Improve from-import error message with location + - 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 69c9383..81c89df 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, *pkgpath; + PyObject *fullmodname, *pkgname, *pkgpath, *pkgname_or_unknown; x = PyObject_GetAttr(v, name); if (x != NULL || !PyErr_ExceptionMatches(PyExc_AttributeError)) @@ -5009,7 +5009,6 @@ import_from(PyObject *v, PyObject *name) goto error; } fullmodname = PyUnicode_FromFormat("%U.%U", pkgname, name); - Py_DECREF(pkgname); if (fullmodname == NULL) { return NULL; } @@ -5018,18 +5017,36 @@ import_from(PyObject *v, PyObject *name) if (x == NULL) { goto error; } + Py_DECREF(pkgname); Py_INCREF(x); return x; error: pkgpath = PyModule_GetFilenameObject(v); + if (pkgname == NULL) { + pkgname_or_unknown = PyUnicode_FromString(""); + if (pkgname_or_unknown == NULL) { + Py_XDECREF(pkgpath); + return NULL; + } + } else { + pkgname_or_unknown = pkgname; + } if (pkgpath == NULL || !PyUnicode_Check(pkgpath)) { PyErr_Clear(); - PyErr_SetImportError(PyUnicode_FromFormat("cannot import name %R", name), pkgname, NULL); + PyErr_SetImportError( + PyUnicode_FromFormat("cannot import name %R from %R (unknown location)", + name, pkgname_or_unknown), + pkgname, NULL); } else { - PyErr_SetImportError(PyUnicode_FromFormat("cannot import name %R", name), pkgname, pkgpath); + PyErr_SetImportError( + PyUnicode_FromFormat("cannot import name %R from %R (%S)", + name, pkgname_or_unknown, pkgpath), + pkgname, pkgpath); } + Py_XDECREF(pkgname_or_unknown); + Py_XDECREF(pkgpath); return NULL; } -- cgit v0.12