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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy