Skip to content

Commit

Permalink
Issue #1559549: Add 'name' and 'path' attributes to ImportError.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
brettcannon committed Apr 13, 2012
1 parent bbfe9e8 commit d41e165
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 3 deletions.
8 changes: 8 additions & 0 deletions Doc/library/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions Include/pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
24 changes: 23 additions & 1 deletion Lib/test/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,8 +902,30 @@ def test_errno_ENOTDIR(self):
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()
4 changes: 4 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
99 changes: 97 additions & 2 deletions Objects/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions Python/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down

0 comments on commit d41e165

Please sign in to comment.