Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 79ec55e

Browse files
committed
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.
1 parent f50b38a commit 79ec55e

6 files changed

Lines changed: 192 additions & 3 deletions

File tree

Doc/library/exceptions.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ The following exceptions are the exceptions that are usually raised.
159159
Raised when an :keyword:`import` statement fails to find the module definition
160160
or when a ``from ... import`` fails to find a name that is to be imported.
161161

162+
The :attr:`name` and :attr:`path` attributes can be set using keyword-only
163+
arguments to the constructor. When set they represent the name of the module
164+
that was attempted to be imported and the path to any file which triggered
165+
the exception, respectively.
166+
167+
.. versionchanged:: 3.3
168+
Added the :attr:`name` and :attr:`path` attributes.
169+
162170

163171
.. exception:: IndexError
164172

Include/pyerrors.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,13 @@ PyAPI_FUNC(PyObject *) PyErr_Format(
231231
...
232232
);
233233

234+
typedef struct {
235+
PyException_HEAD
236+
PyObject *msg;
237+
PyObject *name;
238+
PyObject *path;
239+
} PyImportErrorObject;
240+
234241
#ifdef MS_WINDOWS
235242
PyAPI_FUNC(PyObject *) PyErr_SetFromWindowsErrWithFilename(
236243
int ierr,
@@ -256,6 +263,12 @@ PyAPI_FUNC(PyObject *) PyErr_SetExcFromWindowsErrWithUnicodeFilename(
256263
PyAPI_FUNC(PyObject *) PyErr_SetExcFromWindowsErr(PyObject *, int);
257264
#endif /* MS_WINDOWS */
258265

266+
PyAPI_FUNC(PyObject *) PyErr_SetExcWithArgsKwargs(PyObject *, PyObject *,
267+
PyObject *);
268+
PyAPI_FUNC(PyObject *) PyErr_SetFromImportErrorWithNameAndPath(PyObject *,
269+
PyObject *, PyObject *);
270+
PyAPI_FUNC(PyObject *) PyErr_SetFromImportErrorWithName(PyObject *, PyObject *);
271+
259272
/* Export the old function so that the existing API remains available: */
260273
PyAPI_FUNC(void) PyErr_BadInternalCall(void);
261274
PyAPI_FUNC(void) _PyErr_BadInternalCall(const char *filename, int lineno);

Lib/test/test_exceptions.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,8 +902,30 @@ def test_errno_ENOTDIR(self):
902902
self.assertEqual(cm.exception.errno, errno.ENOTDIR, cm.exception)
903903

904904

905+
class ImportErrorTests(unittest.TestCase):
906+
907+
def test_attributes(self):
908+
# Setting 'name' and 'path' should not be a problem.
909+
exc = ImportError('test')
910+
self.assertIsNone(exc.name)
911+
self.assertIsNone(exc.path)
912+
913+
exc = ImportError('test', name='somemodule')
914+
self.assertEqual(exc.name, 'somemodule')
915+
self.assertIsNone(exc.path)
916+
917+
exc = ImportError('test', path='somepath')
918+
self.assertEqual(exc.path, 'somepath')
919+
self.assertIsNone(exc.name)
920+
921+
exc = ImportError('test', path='somepath', name='somename')
922+
self.assertEqual(exc.name, 'somename')
923+
self.assertEqual(exc.path, 'somepath')
924+
925+
926+
905927
def test_main():
906-
run_unittest(ExceptionTests)
928+
run_unittest(ExceptionTests, ImportErrorTests)
907929

908930
if __name__ == '__main__':
909931
unittest.main()

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Python 3.3.0 Alpha 3?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #1559549: ImportError now has 'name' and 'path' attributes that are set
14+
using keyword arguments to its constructor. They are currently not set by
15+
import as they are meant for use by importlib.
16+
1317
- Issue #14474: Save and restore exception state in thread.start_new_thread()
1418
while writing error message if the thread leaves a unhandled exception.
1519

Objects/exceptions.c

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,9 +605,104 @@ SimpleExtendsException(PyExc_BaseException, KeyboardInterrupt,
605605
/*
606606
* ImportError extends Exception
607607
*/
608-
SimpleExtendsException(PyExc_Exception, ImportError,
609-
"Import can't find module, or can't find name in module.");
610608

609+
static int
610+
ImportError_init(PyImportErrorObject *self, PyObject *args, PyObject *kwds)
611+
{
612+
PyObject *msg = NULL;
613+
PyObject *name = NULL;
614+
PyObject *path = NULL;
615+
616+
/* Macro replacement doesn't allow ## to start the first line of a macro,
617+
so we move the assignment and NULL check into the if-statement. */
618+
#define GET_KWD(kwd) { \
619+
kwd = PyDict_GetItemString(kwds, #kwd); \
620+
if (kwd) { \
621+
Py_CLEAR(self->kwd); \
622+
self->kwd = kwd; \
623+
Py_INCREF(self->kwd);\
624+
if (PyDict_DelItemString(kwds, #kwd)) \
625+
return -1; \
626+
} \
627+
}
628+
629+
if (kwds) {
630+
GET_KWD(name);
631+
GET_KWD(path);
632+
}
633+
634+
if (BaseException_init((PyBaseExceptionObject *)self, args, kwds) == -1)
635+
return -1;
636+
if (PyTuple_GET_SIZE(args) != 1)
637+
return 0;
638+
if (!PyArg_UnpackTuple(args, "ImportError", 1, 1, &msg))
639+
return -1;
640+
641+
Py_CLEAR(self->msg); /* replacing */
642+
self->msg = msg;
643+
Py_INCREF(self->msg);
644+
645+
return 0;
646+
}
647+
648+
static int
649+
ImportError_clear(PyImportErrorObject *self)
650+
{
651+
Py_CLEAR(self->msg);
652+
Py_CLEAR(self->name);
653+
Py_CLEAR(self->path);
654+
return BaseException_clear((PyBaseExceptionObject *)self);
655+
}
656+
657+
static void
658+
ImportError_dealloc(PyImportErrorObject *self)
659+
{
660+
_PyObject_GC_UNTRACK(self);
661+
ImportError_clear(self);
662+
Py_TYPE(self)->tp_free((PyObject *)self);
663+
}
664+
665+
static int
666+
ImportError_traverse(PyImportErrorObject *self, visitproc visit, void *arg)
667+
{
668+
Py_VISIT(self->msg);
669+
Py_VISIT(self->name);
670+
Py_VISIT(self->path);
671+
return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg);
672+
}
673+
674+
static PyObject *
675+
ImportError_str(PyImportErrorObject *self)
676+
{
677+
if (self->msg) {
678+
Py_INCREF(self->msg);
679+
return self->msg;
680+
}
681+
else {
682+
return BaseException_str((PyBaseExceptionObject *)self);
683+
}
684+
}
685+
686+
static PyMemberDef ImportError_members[] = {
687+
{"msg", T_OBJECT, offsetof(PyImportErrorObject, msg), 0,
688+
PyDoc_STR("exception message")},
689+
{"name", T_OBJECT, offsetof(PyImportErrorObject, name), 0,
690+
PyDoc_STR("module name")},
691+
{"path", T_OBJECT, offsetof(PyImportErrorObject, path), 0,
692+
PyDoc_STR("module path")},
693+
{NULL} /* Sentinel */
694+
};
695+
696+
static PyMethodDef ImportError_methods[] = {
697+
{NULL}
698+
};
699+
700+
ComplexExtendsException(PyExc_Exception, ImportError,
701+
ImportError, 0 /* new */,
702+
ImportError_methods, ImportError_members,
703+
0 /* getset */, ImportError_str,
704+
"Import can't find module, or can't find name in "
705+
"module.");
611706

612707
/*
613708
* OSError extends Exception

Python/errors.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,53 @@ PyObject *PyErr_SetFromWindowsErrWithUnicodeFilename(
585585
}
586586
#endif /* MS_WINDOWS */
587587

588+
PyObject *
589+
PyErr_SetExcWithArgsKwargs(PyObject *exc, PyObject *args, PyObject *kwargs)
590+
{
591+
PyObject *val;
592+
593+
/* args must at least be an empty tuple */
594+
if (args == NULL)
595+
args = PyTuple_New(0);
596+
597+
val = PyObject_Call(exc, args, kwargs);
598+
if (val != NULL) {
599+
PyErr_SetObject((PyObject *) Py_TYPE(val), val);
600+
Py_DECREF(val);
601+
}
602+
603+
return NULL;
604+
}
605+
606+
PyObject *
607+
PyErr_SetFromImportErrorWithNameAndPath(PyObject *msg,
608+
PyObject *name, PyObject *path)
609+
{
610+
PyObject *args = PyTuple_New(1);
611+
PyObject *kwargs = PyDict_New();
612+
PyObject *result;
613+
614+
if (path == NULL)
615+
path = Py_None;
616+
617+
PyTuple_SetItem(args, 0, msg);
618+
PyDict_SetItemString(kwargs, "name", name);
619+
PyDict_SetItemString(kwargs, "path", path);
620+
621+
result = PyErr_SetExcWithArgsKwargs(PyExc_ImportError, args, kwargs);
622+
623+
Py_DECREF(args);
624+
Py_DECREF(kwargs);
625+
626+
return result;
627+
}
628+
629+
PyObject *
630+
PyErr_SetFromImportErrorWithName(PyObject *msg, PyObject *name)
631+
{
632+
return PyErr_SetFromImportErrorWithNameAndPath(msg, name, NULL);
633+
}
634+
588635
void
589636
_PyErr_BadInternalCall(const char *filename, int lineno)
590637
{

0 commit comments

Comments
 (0)