diff --git a/Include/pyerrors.h b/Include/pyerrors.h --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -19,6 +19,13 @@ typedef struct { PyException_HEAD PyObject *msg; + PyObject *name; + PyObject *path; +} PyImportErrorObject; + +typedef struct { + PyException_HEAD + PyObject *msg; PyObject *filename; PyObject *lineno; PyObject *offset; diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py --- a/Lib/test/test_import.py +++ b/Lib/test/test_import.py @@ -298,6 +298,22 @@ """)) script_helper.assert_python_ok(testfn) + @unittest.skipUnless(sys.platform == "win32", "Windows specific") + def test_extension_import_fail(self): + # Issue 10854 added `name` and `path` attributes to ImportError + # in order to provide better detail on import failures of extensions. + debug = True if sys.executable[-6:] == "_d.exe" else False + pkg_name = "extension" + pkg_file = pkg_name + "{}".format("_d.pyd" if debug else ".pyd") + with open(pkg_file, "w"): pass + try: + with self.assertRaises(ImportError) as err: + import extension + self.assertEquals(err.exception.name, pkg_name) + self.assertEquals(err.exception.path, pkg_file) + finally: + unlink(pkg_file) + class PycRewritingTests(unittest.TestCase): # Test that the `co_filename` attribute on code objects always points diff --git a/Objects/exceptions.c b/Objects/exceptions.c --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -547,8 +547,81 @@ /* * ImportError extends Exception */ -SimpleExtendsException(PyExc_Exception, ImportError, - "Import can't find module, or can't find name in module."); + +static int +ImportError_init(PyImportErrorObject *self, PyObject *args, PyObject *kwds) +{ + Py_ssize_t size = PyTuple_GET_SIZE(args); + + if (BaseException_init((PyBaseExceptionObject *)self, args, kwds) == -1) + return -1; + + if (size == 0) + return 0; + Py_CLEAR(self->msg); + Py_CLEAR(self->name); + Py_CLEAR(self->path); + if (size >= 1) { + self->msg = PyTuple_GET_ITEM(args, 0); + Py_INCREF(self->msg); + } + if (size >= 2) { + self->name = PyTuple_GET_ITEM(args, 1); + Py_INCREF(self->name); + } + if (size >= 3) { + self->path = PyTuple_GET_ITEM(args, 2); + Py_INCREF(self->path); + } + return 0; +} + +static int +ImportError_clear(PyImportErrorObject *self) +{ + Py_CLEAR(self->msg); + Py_CLEAR(self->name); + Py_CLEAR(self->path); + return BaseException_clear((PyBaseExceptionObject *)self); +} + +static void +ImportError_dealloc(PyImportErrorObject *self) +{ + _PyObject_GC_UNTRACK(self); + ImportError_clear(self); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static int +ImportError_traverse(PyImportErrorObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->msg); + Py_VISIT(self->name); + Py_VISIT(self->path); + return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg); +} + +static PyObject * +ImportError_str(PyImportErrorObject *self) +{ + return PyObject_Str(self->msg); +} + +static PyMemberDef +ImportError_members[] = { + {"msg", T_OBJECT, offsetof(PyImportErrorObject, msg), 0, + PyDoc_STR("Exception message.")}, + {"name", T_OBJECT, offsetof(PyImportErrorObject, name), 0, + PyDoc_STR("Name of object which failed import.")}, + {"path", T_OBJECT, offsetof(PyImportErrorObject, path), 0, + PyDoc_STR("Path of object which failed import.")}, + {NULL} +}; + +ComplexExtendsException(PyExc_Exception, ImportError, ImportError, + ImportError_dealloc, 0, ImportError_members, ImportError_str, + "Import can't find module, or can't find name in module."); /* diff --git a/Python/dynload_win.c b/Python/dynload_win.c --- a/Python/dynload_win.c +++ b/Python/dynload_win.c @@ -203,7 +203,7 @@ SetErrorMode(old_mode); if (hDLL==NULL){ - PyObject *message; + PyObject *message, *exc; unsigned int errorCode; /* Get an error string from Win32 error code */ @@ -248,7 +248,10 @@ theInfo, theLength)); } - PyErr_SetObject(PyExc_ImportError, message); + exc = PyObject_CallFunction(PyExc_ImportError, "(OOO)", message, + PyUnicode_FromString(shortname), + pathname); + PyErr_SetObject(PyExc_ImportError, exc); Py_XDECREF(message); return NULL; } else {