]> granicus.if.org Git - python/commitdiff
Issue #1559549: Add 'name' and 'path' attributes to ImportError.
authorBrett Cannon <brett@python.org>
Fri, 13 Apr 2012 00:24:54 +0000 (20:24 -0400)
committerBrett Cannon <brett@python.org>
Fri, 13 Apr 2012 00:24:54 +0000 (20:24 -0400)
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.

Doc/library/exceptions.rst
Include/pyerrors.h
Lib/test/test_exceptions.py
Misc/NEWS
Objects/exceptions.c
Python/errors.c

index 8c5a9602e43df73d63c53f1dabb3e9ec21696e87..33bc3b06deed17656aff9e227a26886abd409a25 100644 (file)
@@ -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
 
index 1e42ebbb877e322d24cbf46f002caf41c14f250f..a5507059c591dcf8b4c081d5afa99457e524768f 100644 (file)
@@ -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);
index 42536d3b7ca71a8d22c4eb0dd66358fc4197ab9d..39ff85fc19070bdbb3334a0416262dbfc81b604a 100644 (file)
@@ -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()
index c899f12a9cc0fb05a558530b6a84af1145ed13bb..3dab768e998ccf88e1916567fea2317d18551dd2 100644 (file)
--- 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.
 
index bc4379982ea22dd4797601632dde45c5309a70d5..f3dde1121aa83a4b3b6808928d5f9fb687cc6754 100644 (file)
@@ -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
index 31fa9e29556eddfd9fd51c64f3965c34bdf704b3..345a345afe4d1498e50992148efd7e262b21bfb3 100644 (file)
@@ -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)
 {