diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 25f7c1152f2..dc9d7b182da 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -404,6 +404,15 @@ in various ways. There is a separate error indicator for each thread. argument can be used to specify a dictionary of class variables and methods. +.. cfunction:: PyObject* PyErr_NewExceptionWithDoc(char *name, char *doc, PyObject *base, PyObject *dict) + + Same as :cfunc:`PyErr_NewException`, except that the new exception class can + easily be given a docstring: If *doc* is non-*NULL*, it will be used as the + docstring for the exception class. + + .. versionadded:: 3.2 + + .. cfunction:: void PyErr_WriteUnraisable(PyObject *obj) This utility function prints a warning message to ``sys.stderr`` when an diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 400cf649777..06e07050cfc 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -281,6 +281,12 @@ PyErr_NewException:char*:name:: PyErr_NewException:PyObject*:base:0: PyErr_NewException:PyObject*:dict:0: +PyErr_NewExceptionWithDoc:PyObject*::+1: +PyErr_NewExceptionWithDoc:char*:name:: +PyErr_NewExceptionWithDoc:char*:doc:: +PyErr_NewExceptionWithDoc:PyObject*:base:0: +PyErr_NewExceptionWithDoc:PyObject*:dict:0: + PyErr_NoMemory:PyObject*::null: PyErr_NormalizeException:void::: diff --git a/Include/pyerrors.h b/Include/pyerrors.h index 7556e644696..a821e392e21 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -210,8 +210,10 @@ PyAPI_FUNC(void) _PyErr_BadInternalCall(const char *filename, int lineno); #define PyErr_BadInternalCall() _PyErr_BadInternalCall(__FILE__, __LINE__) /* Function to create a new exception */ -PyAPI_FUNC(PyObject *) PyErr_NewException(const char *name, PyObject *base, - PyObject *dict); +PyAPI_FUNC(PyObject *) PyErr_NewException( + const char *name, PyObject *base, PyObject *dict); +PyAPI_FUNC(PyObject *) PyErr_NewExceptionWithDoc( + const char *name, const char *doc, PyObject *base, PyObject *dict); PyAPI_FUNC(void) PyErr_WriteUnraisable(PyObject *); /* In sigcheck.c or signalmodule.c */ diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 404b5d15703..5be67570326 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -618,6 +618,46 @@ class ExceptionTests(unittest.TestCase): tb2 = raiseMemError() self.assertEqual(tb1, tb2) + def test_exception_with_doc(self): + import _testcapi + doc2 = "This is a test docstring." + doc4 = "This is another test docstring." + + self.assertRaises(SystemError, _testcapi.make_exception_with_doc, + "error1") + + # test basic usage of PyErr_NewException + error1 = _testcapi.make_exception_with_doc("_testcapi.error1") + self.assertIs(type(error1), type) + self.assertTrue(issubclass(error1, Exception)) + self.assertIsNone(error1.__doc__) + + # test with given docstring + error2 = _testcapi.make_exception_with_doc("_testcapi.error2", doc2) + self.assertEqual(error2.__doc__, doc2) + + # test with explicit base (without docstring) + error3 = _testcapi.make_exception_with_doc("_testcapi.error3", + base=error2) + self.assertTrue(issubclass(error3, error2)) + + # test with explicit base tuple + class C(object): + pass + error4 = _testcapi.make_exception_with_doc("_testcapi.error4", doc4, + (error3, C)) + self.assertTrue(issubclass(error4, error3)) + self.assertTrue(issubclass(error4, C)) + self.assertEqual(error4.__doc__, doc4) + + # test with explicit dictionary + error5 = _testcapi.make_exception_with_doc("_testcapi.error5", "", + error4, {'a': 1}) + self.assertTrue(issubclass(error5, error4)) + self.assertEqual(error5.a, 1) + self.assertEqual(error5.__doc__, "") + + def test_main(): run_unittest(ExceptionTests) diff --git a/Misc/NEWS b/Misc/NEWS index ac10bed9315..368cec651a2 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -136,6 +136,8 @@ Core and Builtins C-API ----- +- Issue #7033: function ``PyErr_NewExceptionWithDoc()`` added. + - Issue #7414: 'C' code wasn't being skipped properly (for keyword arguments) in PyArg_ParseTupleAndKeywords. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index e549151ec93..19afbf283af 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1716,6 +1716,26 @@ code_newempty(PyObject *self, PyObject *args) return (PyObject *)PyCode_NewEmpty(filename, funcname, firstlineno); } +/* Test PyErr_NewExceptionWithDoc (also exercise PyErr_NewException). + Run via Lib/test/test_exceptions.py */ +static PyObject * +make_exception_with_doc(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *name; + const char *doc = NULL; + PyObject *base = NULL; + PyObject *dict = NULL; + + static char *kwlist[] = {"name", "doc", "base", "dict", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "s|sOO:make_exception_with_doc", kwlist, + &name, &doc, &base, &dict)) + return NULL; + + return PyErr_NewExceptionWithDoc(name, doc, base, dict); +} + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, {"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS}, @@ -1774,6 +1794,8 @@ static PyMethodDef TestMethods[] = { {"exception_print", exception_print, METH_VARARGS}, {"argparsing", argparsing, METH_VARARGS}, {"code_newempty", code_newempty, METH_VARARGS}, + {"make_exception_with_doc", (PyCFunction)make_exception_with_doc, + METH_VARARGS | METH_KEYWORDS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/errors.c b/Python/errors.c index 2169a1ab7a9..42e0e6f3d5a 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -693,6 +693,41 @@ PyErr_NewException(const char *name, PyObject *base, PyObject *dict) return result; } + +/* Create an exception with docstring */ +PyObject * +PyErr_NewExceptionWithDoc(const char *name, const char *doc, + PyObject *base, PyObject *dict) +{ + int result; + PyObject *ret = NULL; + PyObject *mydict = NULL; /* points to the dict only if we create it */ + PyObject *docobj; + + if (dict == NULL) { + dict = mydict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + } + + if (doc != NULL) { + docobj = PyUnicode_FromString(doc); + if (docobj == NULL) + goto failure; + result = PyDict_SetItemString(dict, "__doc__", docobj); + Py_DECREF(docobj); + if (result < 0) + goto failure; + } + + ret = PyErr_NewException(name, base, dict); + failure: + Py_XDECREF(mydict); + return ret; +} + + /* Call when an exception has occurred but there is no way for Python to handle it. Examples: exception in __del__ or during GC. */ void