]> granicus.if.org Git - python/commitdiff
bpo-29546: Improve from-import error message with location (#103)
authorMatthias Bussonnier <bussonniermatthias@gmail.com>
Wed, 22 Feb 2017 15:06:50 +0000 (07:06 -0800)
committerBarry Warsaw <barry@python.org>
Wed, 22 Feb 2017 15:06:50 +0000 (10:06 -0500)
bpo-29546: Improve from-import error message with location

Doc/whatsnew/3.7.rst
Lib/test/test_import/__init__.py
Misc/NEWS
Python/ceval.c

index 21621c5ee4eac9de20db20499affe3345f6a2c72..f6367fd1f4a672dddc08a254147f9630d2b4ade3 100644 (file)
@@ -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
 ===========
index df678f17f18e4e1b28997bda6265208a79fb452a..ace1a46f9b91a72e19f0f0e63b7842dd0a2f7323 100644 (file)
@@ -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 module name>' (unknown location)")
+
 
 @skip_if_dont_write_bytecode
 class FilePermissionTests(unittest.TestCase):
index 57e5ab9c20fe71abfaedd84b11955a6880699e13..3fe9182924e6297f4361175281dec826ae3d11c3 100644 (file)
--- 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.
index 69c9383841960979d5abcd38ebd67c22923bbcd3..81c89dfadfaac02cee6b31fe76bca3495d802218 100644 (file)
@@ -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("<unknown module name>");
+        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;
 }