Skip to content

Commit 740cdc3

Browse files
committed
#7033: add new API function PyErr_NewExceptionWithDoc, for easily giving new exceptions a docstring.
1 parent 02e7dfd commit 740cdc3

File tree

7 files changed

+119
-2
lines changed

7 files changed

+119
-2
lines changed

Doc/c-api/exceptions.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,15 @@ is a separate error indicator for each thread.
433433
argument can be used to specify a dictionary of class variables and methods.
434434

435435

436+
.. cfunction:: PyObject* PyErr_NewExceptionWithDoc(char *name, char *doc, PyObject *base, PyObject *dict)
437+
438+
Same as :cfunc:`PyErr_NewException`, except that the new exception class can
439+
easily be given a docstring: If *doc* is non-*NULL*, it will be used as the
440+
docstring for the exception class.
441+
442+
.. versionadded:: 2.7
443+
444+
436445
.. cfunction:: void PyErr_WriteUnraisable(PyObject *obj)
437446

438447
This utility function prints a warning message to ``sys.stderr`` when an

Doc/data/refcounts.dat

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,12 @@ PyErr_NewException:char*:name::
242242
PyErr_NewException:PyObject*:base:0:
243243
PyErr_NewException:PyObject*:dict:0:
244244

245+
PyErr_NewExceptionWithDoc:PyObject*::+1:
246+
PyErr_NewExceptionWithDoc:char*:name::
247+
PyErr_NewExceptionWithDoc:char*:doc::
248+
PyErr_NewExceptionWithDoc:PyObject*:base:0:
249+
PyErr_NewExceptionWithDoc:PyObject*:dict:0:
250+
245251
PyErr_NoMemory:PyObject*::null:
246252

247253
PyErr_NormalizeException:void:::

Include/pyerrors.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,10 @@ PyAPI_FUNC(void) _PyErr_BadInternalCall(char *filename, int lineno);
220220
#define PyErr_BadInternalCall() _PyErr_BadInternalCall(__FILE__, __LINE__)
221221

222222
/* Function to create a new exception */
223-
PyAPI_FUNC(PyObject *) PyErr_NewException(char *name, PyObject *base,
224-
PyObject *dict);
223+
PyAPI_FUNC(PyObject *) PyErr_NewException(
224+
char *name, PyObject *base, PyObject *dict);
225+
PyAPI_FUNC(PyObject *) PyErr_NewExceptionWithDoc(
226+
char *name, char *doc, PyObject *base, PyObject *dict);
225227
PyAPI_FUNC(void) PyErr_WriteUnraisable(PyObject *);
226228

227229
/* In sigcheck.c or signalmodule.c */

Lib/test/test_exceptions.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,45 @@ def test_many_args_with_overridden___str__(self):
537537
self.assertRaises(UnicodeEncodeError, str, e)
538538
self.assertEqual(unicode(e), u'f\xf6\xf6')
539539

540+
def test_exception_with_doc(self):
541+
import _testcapi
542+
doc2 = "This is a test docstring."
543+
doc4 = "This is another test docstring."
544+
545+
self.assertRaises(SystemError, _testcapi.make_exception_with_doc,
546+
"error1")
547+
548+
# test basic usage of PyErr_NewException
549+
error1 = _testcapi.make_exception_with_doc("_testcapi.error1")
550+
self.assertIs(type(error1), type)
551+
self.assertTrue(issubclass(error1, Exception))
552+
self.assertIsNone(error1.__doc__)
553+
554+
# test with given docstring
555+
error2 = _testcapi.make_exception_with_doc("_testcapi.error2", doc2)
556+
self.assertEqual(error2.__doc__, doc2)
557+
558+
# test with explicit base (without docstring)
559+
error3 = _testcapi.make_exception_with_doc("_testcapi.error3",
560+
base=error2)
561+
self.assertTrue(issubclass(error3, error2))
562+
563+
# test with explicit base tuple
564+
class C(object):
565+
pass
566+
error4 = _testcapi.make_exception_with_doc("_testcapi.error4", doc4,
567+
(error3, C))
568+
self.assertTrue(issubclass(error4, error3))
569+
self.assertTrue(issubclass(error4, C))
570+
self.assertEqual(error4.__doc__, doc4)
571+
572+
# test with explicit dictionary
573+
error5 = _testcapi.make_exception_with_doc("_testcapi.error5", "",
574+
error4, {'a': 1})
575+
self.assertTrue(issubclass(error5, error4))
576+
self.assertEqual(error5.a, 1)
577+
self.assertEqual(error5.__doc__, "")
578+
540579

541580
def test_main():
542581
run_unittest(ExceptionTests, TestSameStrAndUnicodeMsg)

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ Library
7070
- Issue #7457: added a read_pkg_file method to
7171
distutils.dist.DistributionMetadata.
7272

73+
C-API
74+
-----
75+
76+
- Issue #7033: function ``PyErr_NewExceptionWithDoc()`` added.
77+
7378
Build
7479
-----
7580

Modules/_testcapimodule.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,26 @@ code_newempty(PyObject *self, PyObject *args)
11981198
return (PyObject *)PyCode_NewEmpty(filename, funcname, firstlineno);
11991199
}
12001200

1201+
/* Test PyErr_NewExceptionWithDoc (also exercise PyErr_NewException).
1202+
Run via Lib/test/test_exceptions.py */
1203+
static PyObject *
1204+
make_exception_with_doc(PyObject *self, PyObject *args, PyObject *kwargs)
1205+
{
1206+
char *name;
1207+
char *doc = NULL;
1208+
PyObject *base = NULL;
1209+
PyObject *dict = NULL;
1210+
1211+
static char *kwlist[] = {"name", "doc", "base", "dict", NULL};
1212+
1213+
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
1214+
"s|sOO:make_exception_with_doc", kwlist,
1215+
&name, &doc, &base, &dict))
1216+
return NULL;
1217+
1218+
return PyErr_NewExceptionWithDoc(name, doc, base, dict);
1219+
}
1220+
12011221
static PyMethodDef TestMethods[] = {
12021222
{"raise_exception", raise_exception, METH_VARARGS},
12031223
{"test_config", (PyCFunction)test_config, METH_NOARGS},
@@ -1248,6 +1268,8 @@ static PyMethodDef TestMethods[] = {
12481268
#endif
12491269
{"traceback_print", traceback_print, METH_VARARGS},
12501270
{"code_newempty", code_newempty, METH_VARARGS},
1271+
{"make_exception_with_doc", (PyCFunction)make_exception_with_doc,
1272+
METH_VARARGS | METH_KEYWORDS},
12511273
{NULL, NULL} /* sentinel */
12521274
};
12531275

Python/errors.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,40 @@ PyErr_NewException(char *name, PyObject *base, PyObject *dict)
604604
return result;
605605
}
606606

607+
608+
/* Create an exception with docstring */
609+
PyObject *
610+
PyErr_NewExceptionWithDoc(char *name, char *doc, PyObject *base, PyObject *dict)
611+
{
612+
int result;
613+
PyObject *ret = NULL;
614+
PyObject *mydict = NULL; /* points to the dict only if we create it */
615+
PyObject *docobj;
616+
617+
if (dict == NULL) {
618+
dict = mydict = PyDict_New();
619+
if (dict == NULL) {
620+
return NULL;
621+
}
622+
}
623+
624+
if (doc != NULL) {
625+
docobj = PyString_FromString(doc);
626+
if (docobj == NULL)
627+
goto failure;
628+
result = PyDict_SetItemString(dict, "__doc__", docobj);
629+
Py_DECREF(docobj);
630+
if (result < 0)
631+
goto failure;
632+
}
633+
634+
ret = PyErr_NewException(name, base, dict);
635+
failure:
636+
Py_XDECREF(mydict);
637+
return ret;
638+
}
639+
640+
607641
/* Call when an exception has occurred but there is no way for Python
608642
to handle it. Examples: exception in __del__ or during GC. */
609643
void

0 commit comments

Comments
 (0)