Skip to content

Commit a181463

Browse files
committed
bpo-29546: Improve from-import error message with location
Add location information like canonical module name where identifier cannot be found and file location if available. First iteration of this was pythongh-91
1 parent 112ec38 commit a181463

File tree

4 files changed

+32
-4
lines changed

4 files changed

+32
-4
lines changed

Doc/whatsnew/3.7.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ Other Language Changes
8383
whitespace, not only spaces.
8484
(Contributed by Robert Xiao in :issue:`28927`.)
8585

86+
* :exc:`ImportError` now displays module name and module ``__file__`` path when
87+
``from ... import ...`` fails. :issue:`29546`.
88+
8689

8790
New Modules
8891
===========

Lib/test/test_import/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ def test_from_import_missing_attr_has_name_and_path(self):
8585
from os import i_dont_exist
8686
self.assertEqual(cm.exception.name, 'os')
8787
self.assertEqual(cm.exception.path, os.__file__)
88+
self.assertRegex(str(cm.exception), "cannot import name 'i_dont_exist' from 'os' \(.*/Lib/os.py\)")
89+
90+
def test_from_import_missing_attr_has_name_and_so_path(self):
91+
import _opcode
92+
with self.assertRaises(ImportError) as cm:
93+
from _opcode import i_dont_exist
94+
self.assertEqual(cm.exception.name, '_opcode')
95+
self.assertEqual(cm.exception.path, _opcode.__file__)
96+
self.assertRegex(str(cm.exception), "cannot import name 'i_dont_exist' from '_opcode' \(.*\.(so|dll)\)")
8897

8998
def test_from_import_missing_attr_has_name(self):
9099
with self.assertRaises(ImportError) as cm:
@@ -365,9 +374,12 @@ def __getattr__(self, _):
365374
module_name = 'test_from_import_AttributeError'
366375
self.addCleanup(unload, module_name)
367376
sys.modules[module_name] = AlwaysAttributeError()
368-
with self.assertRaises(ImportError):
377+
with self.assertRaises(ImportError) as cm:
369378
from test_from_import_AttributeError import does_not_exist
370379

380+
self.assertEqual(str(cm.exception),
381+
"cannot import name 'does_not_exist' from '<unknown module name>' (unknown location)")
382+
371383

372384
@skip_if_dont_write_bytecode
373385
class FilePermissionTests(unittest.TestCase):

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Core and Builtins
2121

2222
- bpo-29546: Set the 'path' and 'name' attribute on ImportError for ``from ... import ...``.
2323

24+
- bpo-29546: Improve from-import error message with location
25+
2426
- Issue #29319: Prevent RunMainFromImporter overwriting sys.path[0].
2527

2628
- Issue #29337: Fixed possible BytesWarning when compare the code objects.

Python/ceval.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4995,7 +4995,7 @@ import_from(PyObject *v, PyObject *name)
49954995
{
49964996
PyObject *x;
49974997
_Py_IDENTIFIER(__name__);
4998-
PyObject *fullmodname, *pkgname, *pkgpath;
4998+
PyObject *fullmodname, *pkgname, *pkgpath, *pkgname_or_unknown;
49994999

50005000
x = PyObject_GetAttr(v, name);
50015001
if (x != NULL || !PyErr_ExceptionMatches(PyExc_AttributeError))
@@ -5022,12 +5022,23 @@ import_from(PyObject *v, PyObject *name)
50225022
return x;
50235023
error:
50245024
pkgpath = PyModule_GetFilenameObject(v);
5025+
if (pkgname == NULL) {
5026+
pkgname_or_unknown = PyUnicode_FromString("<unknown module name>");
5027+
} else {
5028+
pkgname_or_unknown = pkgname;
5029+
}
50255030

50265031
if (pkgpath == NULL || !PyUnicode_Check(pkgpath)) {
50275032
PyErr_Clear();
5028-
PyErr_SetImportError(PyUnicode_FromFormat("cannot import name %R", name), pkgname, NULL);
5033+
PyErr_SetImportError(
5034+
PyUnicode_FromFormat("cannot import name %R from %R (unknown location)",
5035+
name, pkgname_or_unknown),
5036+
pkgname, NULL);
50295037
} else {
5030-
PyErr_SetImportError(PyUnicode_FromFormat("cannot import name %R", name), pkgname, pkgpath);
5038+
PyErr_SetImportError(
5039+
PyUnicode_FromFormat("cannot import name %R from %R (%S)",
5040+
name, pkgname_or_unknown, pkgpath),
5041+
pkgname, pkgpath);
50315042
}
50325043

50335044
return NULL;

0 commit comments

Comments
 (0)