summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/exceptions.rst8
-rw-r--r--Include/pyerrors.h13
-rw-r--r--Lib/test/test_exceptions.py24
-rw-r--r--Misc/NEWS4
-rw-r--r--Objects/exceptions.c99
-rw-r--r--Python/errors.c47
6 files changed, 192 insertions, 3 deletions
diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst
index 8c5a960..33bc3b0 100644
--- a/Doc/library/exceptions.rst
+++ b/Doc/library/exceptions.rst
@@ -159,6 +159,14 @@ The following exceptions are the exceptions that are usually raised.
Raised when an :keyword:`import` statement fails to find the module definition
or when a ``from ... import`` fails to find a name that is to be imported.
+ The :attr:`name` and :attr:`path` attributes can be set using keyword-only
+ arguments to the constructor. When set they represent the name of the module
+ that was attempted to be imported and the path to any file which triggered
+ the exception, respectively.
+
+ .. versionchanged:: 3.3
+ Added the :attr:`name` and :attr:`path` attributes.
+
.. exception:: IndexError
diff --git a/Include/pyerrors.h b/Include/pyerrors.h
index 1e42ebb..a550705 100644
--- a/Include/pyerrors.h
+++ b/Include/pyerrors.h
@@ -231,6 +231,13 @@ PyAPI_FUNC(PyObject *) PyErr_Format(
...
);
+typedef struct {
+ PyException_HEAD
+ PyObject *msg;
+ PyObject *name;
+ PyObject *path;
+} PyImportErrorObject;
+
#ifdef MS_WINDOWS
PyAPI_FUNC(PyObject *) PyErr_SetFromWindowsErrWithFilename(
int ierr,
@@ -256,6 +263,12 @@ PyAPI_FUNC(PyObject *) PyErr_SetExcFromWindowsErrWithUnicodeFilename(
PyAPI_FUNC(PyObject *) PyErr_SetExcFromWindowsErr(PyObject *, int);
#endif /* MS_WINDOWS */
+PyAPI_FUNC(PyObject *) PyErr_SetExcWithArgsKwargs(PyObject *, PyObject *,
+ PyObject *);
+PyAPI_FUNC(PyObject *) PyErr_SetFromImportErrorWithNameAndPath(PyObject *,
+ PyObject *, PyObject *);
+PyAPI_FUNC(PyObject *) PyErr_SetFromImportErrorWithName(PyObject *, PyObject *);
+
/* Export the old function so that the existing API remains available: */
PyAPI_FUNC(void) PyErr_BadInternalCall(void);
PyAPI_FUNC(void) _PyErr_BadInternalCall(const char *filename, int lineno);
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 42536d3..39ff85f 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -902,8 +902,30 @@ class ExceptionTests(unittest.TestCase):
self.assertEqual(cm.exception.errno, errno.ENOTDIR, cm.exception)
+class ImportErrorTests(unittest.TestCase):
+
+ def test_attributes(self):
+ # Setting 'name' and 'path' should not be a problem.
+ exc = ImportError('test')
+ self.assertIsNone(exc.name)
+ self.assertIsNone(exc.path)
+
+ exc = ImportError('test', name='somemodule')
+ self.assertEqual(exc.name, 'somemodule')
+ self.assertIsNone(exc.path)
+
+ exc = ImportError('test', path='somepath')
+ self.assertEqual(exc.path, 'somepath')
+ self.assertIsNone(exc.name)
+
+ exc = ImportError('test', path='somepath', name='somename')
+ self.assertEqual(exc.name, 'somename')
+ self.assertEqual(exc.path, 'somepath')
+
+
+
def test_main():
- run_unittest(ExceptionTests)
+ run_unittest(ExceptionTests, ImportErrorTests)
if __name__ == '__main__':
unittest.main()
diff --git a/Misc/NEWS b/Misc/NEWS
index c899f12..3dab768e 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,10 @@ What's New in Python 3.3.0 Alpha 3?
Core and Builtins
-----------------
+- Issue #1559549: ImportError now has 'name' and 'path' attributes that are set
+ using keyword arguments to its constructor. They are currently not set by
+ import as they are meant for use by importlib.
+
- Issue #14474: Save and restore exception state in thread.start_new_thread()
while writing error message if the thread leaves a unhandled exception.
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index bc43799..f3dde11 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -605,9 +605,104 @@ SimpleExtendsException(PyExc_BaseException, KeyboardInterrupt,
/*
* 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)
+{
+ PyObject *msg = NULL;
+ PyObject *name = NULL;
+ PyObject *path = NULL;
+
+/* Macro replacement doesn't allow ## to start the first line of a macro,
+ so we move the assignment and NULL check into the if-statement. */
+#define GET_KWD(kwd) { \
+ kwd = PyDict_GetItemString(kwds, #kwd); \
+ if (kwd) { \
+ Py_CLEAR(self->kwd); \
+ self->kwd = kwd; \
+ Py_INCREF(self->kwd);\
+ if (PyDict_DelItemString(kwds, #kwd)) \
+ return -1; \
+ } \
+ }
+
+ if (kwds) {
+ GET_KWD(name);
+ GET_KWD(path);
+ }
+
+ if (BaseException_init((PyBaseExceptionObject *)self, args, kwds) == -1)
+ return -1;
+ if (PyTuple_GET_SIZE(args) != 1)
+ return 0;
+ if (!PyArg_UnpackTuple(args, "ImportError", 1, 1, &msg))
+ return -1;
+
+ Py_CLEAR(self->msg); /* replacing */
+ self->msg = msg;
+ Py_INCREF(self->msg);
+
+ 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)
+{
+ if (self->msg) {
+ Py_INCREF(self->msg);
+ return self->msg;
+ }
+ else {
+ return BaseException_str((PyBaseExceptionObject *)self);
+ }
+}
+
+static PyMemberDef ImportError_members[] = {
+ {"msg", T_OBJECT, offsetof(PyImportErrorObject, msg), 0,
+ PyDoc_STR("exception message")},
+ {"name", T_OBJECT, offsetof(PyImportErrorObject, name), 0,
+ PyDoc_STR("module name")},
+ {"path", T_OBJECT, offsetof(PyImportErrorObject, path), 0,
+ PyDoc_STR("module path")},
+ {NULL} /* Sentinel */
+};
+
+static PyMethodDef ImportError_methods[] = {
+ {NULL}
+};
+
+ComplexExtendsException(PyExc_Exception, ImportError,
+ ImportError, 0 /* new */,
+ ImportError_methods, ImportError_members,
+ 0 /* getset */, ImportError_str,
+ "Import can't find module, or can't find name in "
+ "module.");
/*
* OSError extends Exception
diff --git a/Python/errors.c b/Python/errors.c
index 31fa9e2..345a345 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -585,6 +585,53 @@ PyObject *PyErr_SetFromWindowsErrWithUnicodeFilename(
}
#endif /* MS_WINDOWS */
+PyObject *
+PyErr_SetExcWithArgsKwargs(PyObject *exc, PyObject *args, PyObject *kwargs)
+{
+ PyObject *val;
+
+ /* args must at least be an empty tuple */
+ if (args == NULL)
+ args = PyTuple_New(0);
+
+ val = PyObject_Call(exc, args, kwargs);
+ if (val != NULL) {
+ PyErr_SetObject((PyObject *) Py_TYPE(val), val);
+ Py_DECREF(val);
+ }
+
+ return NULL;
+}
+
+PyObject *
+PyErr_SetFromImportErrorWithNameAndPath(PyObject *msg,
+ PyObject *name, PyObject *path)
+{
+ PyObject *args = PyTuple_New(1);
+ PyObject *kwargs = PyDict_New();
+ PyObject *result;
+
+ if (path == NULL)
+ path = Py_None;
+
+ PyTuple_SetItem(args, 0, msg);
+ PyDict_SetItemString(kwargs, "name", name);
+ PyDict_SetItemString(kwargs, "path", path);
+
+ result = PyErr_SetExcWithArgsKwargs(PyExc_ImportError, args, kwargs);
+
+ Py_DECREF(args);
+ Py_DECREF(kwargs);
+
+ return result;
+}
+
+PyObject *
+PyErr_SetFromImportErrorWithName(PyObject *msg, PyObject *name)
+{
+ return PyErr_SetFromImportErrorWithNameAndPath(msg, name, NULL);
+}
+
void
_PyErr_BadInternalCall(const char *filename, int lineno)
{