summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrett Cannon <brett@python.org>2012-04-12 20:24:54 -0400
committerBrett Cannon <brett@python.org>2012-04-12 20:24:54 -0400
commit79ec55e980d7b205bbc78d44e0892d0ef37d3abb (patch)
tree7fca6e94007fd848ec36ba029aee3b1ec2a8785c
parentf50b38a11fa951582b7f1656685201269f265784 (diff)
downloadcpython-git-79ec55e980d7b205bbc78d44e0892d0ef37d3abb.tar.gz
Issue #1559549: Add 'name' and 'path' attributes to ImportError.
Currently import does not use these attributes as they are planned for use by importlib (which will be another commit). Thanks to Filip GruszczyƄski for the initial patch and Brian Curtin for refining it.
-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 8c5a9602e4..33bc3b06de 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 1e42ebbb87..a5507059c5 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 42536d3b7c..39ff85fc19 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 c899f12a9c..3dab768e99 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 bc4379982e..f3dde1121a 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 31fa9e2955..345a345afe 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)
{