Skip to content

Commit c87b66b

Browse files
Yoav11serhiy-storchakaarhadthedevynir3
authored
gh-74185: repr() of ImportError now contains attributes name and path (#136770)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Oleg Iarygin <oleg@arhadthedev.net> Co-authored-by: ynir3 <ynir3@bloomberg.net>
1 parent c47ffbf commit c87b66b

File tree

4 files changed

+128
-6
lines changed

4 files changed

+128
-6
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ Other language changes
204204
controlled by :ref:`environment variables <using-on-controlling-color>`.
205205
(Contributed by Peter Bierma in :gh:`134170`.)
206206

207+
* The :meth:`~object.__repr__` of :class:`ImportError` and :class:`ModuleNotFoundError`
208+
now shows "name" and "path" as ``name=<name>`` and ``path=<path>`` if they were given
209+
as keyword arguments at construction time.
210+
(Contributed by Serhiy Storchaka, Oleg Iarygin, and Yoav Nir in :gh:`74185`.)
207211

208212
New modules
209213
===========

Lib/test/test_exceptions.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2079,6 +2079,50 @@ def test_copy_pickle(self):
20792079
self.assertEqual(exc.name, orig.name)
20802080
self.assertEqual(exc.path, orig.path)
20812081

2082+
def test_repr(self):
2083+
exc = ImportError()
2084+
self.assertEqual(repr(exc), "ImportError()")
2085+
2086+
exc = ImportError('test')
2087+
self.assertEqual(repr(exc), "ImportError('test')")
2088+
2089+
exc = ImportError('test', 'case')
2090+
self.assertEqual(repr(exc), "ImportError('test', 'case')")
2091+
2092+
exc = ImportError(name='somemodule')
2093+
self.assertEqual(repr(exc), "ImportError(name='somemodule')")
2094+
2095+
exc = ImportError('test', name='somemodule')
2096+
self.assertEqual(repr(exc), "ImportError('test', name='somemodule')")
2097+
2098+
exc = ImportError(path='somepath')
2099+
self.assertEqual(repr(exc), "ImportError(path='somepath')")
2100+
2101+
exc = ImportError('test', path='somepath')
2102+
self.assertEqual(repr(exc), "ImportError('test', path='somepath')")
2103+
2104+
exc = ImportError(name='somename', path='somepath')
2105+
self.assertEqual(repr(exc),
2106+
"ImportError(name='somename', path='somepath')")
2107+
2108+
exc = ImportError('test', name='somename', path='somepath')
2109+
self.assertEqual(repr(exc),
2110+
"ImportError('test', name='somename', path='somepath')")
2111+
2112+
exc = ModuleNotFoundError('test', name='somename', path='somepath')
2113+
self.assertEqual(repr(exc),
2114+
"ModuleNotFoundError('test', name='somename', path='somepath')")
2115+
2116+
def test_ModuleNotFoundError_repr_with_failed_import(self):
2117+
with self.assertRaises(ModuleNotFoundError) as cm:
2118+
import does_not_exist # type: ignore[import] # noqa: F401
2119+
2120+
self.assertEqual(cm.exception.name, "does_not_exist")
2121+
self.assertIsNone(cm.exception.path)
2122+
2123+
self.assertEqual(repr(cm.exception),
2124+
"ModuleNotFoundError(\"No module named 'does_not_exist'\", name='does_not_exist')")
2125+
20822126

20832127
def run_script(source):
20842128
if isinstance(source, str):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The :meth:`~object.__repr__` of :class:`ImportError` and :class:`ModuleNotFoundError`
2+
now shows "name" and "path" as ``name=<name>`` and ``path=<path>`` if they were given
3+
as keyword arguments at construction time.
4+
Patch by Serhiy Storchaka, Oleg Iarygin, and Yoav Nir

Objects/exceptions.c

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,62 @@ ImportError_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
18641864
return res;
18651865
}
18661866

1867+
static PyObject *
1868+
ImportError_repr(PyObject *self)
1869+
{
1870+
int hasargs = PyTuple_GET_SIZE(((PyBaseExceptionObject *)self)->args) != 0;
1871+
PyImportErrorObject *exc = PyImportErrorObject_CAST(self);
1872+
if (exc->name == NULL && exc->path == NULL) {
1873+
return BaseException_repr(self);
1874+
}
1875+
PyUnicodeWriter *writer = PyUnicodeWriter_Create(0);
1876+
if (writer == NULL) {
1877+
goto error;
1878+
}
1879+
PyObject *r = BaseException_repr(self);
1880+
if (r == NULL) {
1881+
goto error;
1882+
}
1883+
if (PyUnicodeWriter_WriteSubstring(
1884+
writer, r, 0, PyUnicode_GET_LENGTH(r) - 1) < 0)
1885+
{
1886+
Py_DECREF(r);
1887+
goto error;
1888+
}
1889+
Py_DECREF(r);
1890+
if (exc->name) {
1891+
if (hasargs) {
1892+
if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
1893+
goto error;
1894+
}
1895+
}
1896+
if (PyUnicodeWriter_Format(writer, "name=%R", exc->name) < 0) {
1897+
goto error;
1898+
}
1899+
hasargs = 1;
1900+
}
1901+
if (exc->path) {
1902+
if (hasargs) {
1903+
if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) {
1904+
goto error;
1905+
}
1906+
}
1907+
if (PyUnicodeWriter_Format(writer, "path=%R", exc->path) < 0) {
1908+
goto error;
1909+
}
1910+
}
1911+
1912+
if (PyUnicodeWriter_WriteChar(writer, ')') < 0) {
1913+
goto error;
1914+
}
1915+
1916+
return PyUnicodeWriter_Finish(writer);
1917+
1918+
error:
1919+
PyUnicodeWriter_Discard(writer);
1920+
return NULL;
1921+
}
1922+
18671923
static PyMemberDef ImportError_members[] = {
18681924
{"msg", _Py_T_OBJECT, offsetof(PyImportErrorObject, msg), 0,
18691925
PyDoc_STR("exception message")},
@@ -1881,12 +1937,26 @@ static PyMethodDef ImportError_methods[] = {
18811937
{NULL}
18821938
};
18831939

1884-
ComplexExtendsException(PyExc_Exception, ImportError,
1885-
ImportError, 0 /* new */,
1886-
ImportError_methods, ImportError_members,
1887-
0 /* getset */, ImportError_str,
1888-
"Import can't find module, or can't find name in "
1889-
"module.");
1940+
static PyTypeObject _PyExc_ImportError = {
1941+
PyVarObject_HEAD_INIT(NULL, 0)
1942+
.tp_name = "ImportError",
1943+
.tp_basicsize = sizeof(PyImportErrorObject),
1944+
.tp_dealloc = ImportError_dealloc,
1945+
.tp_repr = ImportError_repr,
1946+
.tp_str = ImportError_str,
1947+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
1948+
.tp_doc = PyDoc_STR(
1949+
"Import can't find module, "
1950+
"or can't find name in module."),
1951+
.tp_traverse = ImportError_traverse,
1952+
.tp_clear = ImportError_clear,
1953+
.tp_methods = ImportError_methods,
1954+
.tp_members = ImportError_members,
1955+
.tp_base = &_PyExc_Exception,
1956+
.tp_dictoffset = offsetof(PyImportErrorObject, dict),
1957+
.tp_init = ImportError_init,
1958+
};
1959+
PyObject *PyExc_ImportError = (PyObject *)&_PyExc_ImportError;
18901960

18911961
/*
18921962
* ModuleNotFoundError extends ImportError

0 commit comments

Comments
 (0)