Index: Python/errors.c =================================================================== --- Python/errors.c (revision 76959) +++ Python/errors.c (working copy) @@ -604,6 +604,30 @@ return result; } +/* Create and document an exception */ +PyObject * +PyErr_NewExceptionWithDoc(char *name, const char *doc, PyObject *base) +{ + PyObject *dict = NULL; + PyObject *_doc; + + if (doc != NULL) { + dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + _doc = PyString_FromString(doc); + if (_doc == NULL || PyDict_SetItemString(dict, "__doc__", _doc) != 0) { + Py_XDECREF(_doc); + Py_DECREF(dict); + return NULL; + } + Py_DECREF(_doc); + } + + return PyErr_NewException(name, base, dict); +} + /* Call when an exception has occurred but there is no way for Python to handle it. Examples: exception in __del__ or during GC. */ void Index: Include/pyerrors.h =================================================================== --- Include/pyerrors.h (revision 76959) +++ Include/pyerrors.h (working copy) @@ -222,6 +222,8 @@ /* Function to create a new exception */ PyAPI_FUNC(PyObject *) PyErr_NewException(char *name, PyObject *base, PyObject *dict); +PyAPI_FUNC(PyObject *) PyErr_NewExceptionWithDoc(char *name, const char *doc, + PyObject *base); PyAPI_FUNC(void) PyErr_WriteUnraisable(PyObject *); /* In sigcheck.c or signalmodule.c */ Index: Doc/c-api/exceptions.rst =================================================================== --- Doc/c-api/exceptions.rst (revision 76959) +++ Doc/c-api/exceptions.rst (working copy) @@ -433,6 +433,18 @@ argument can be used to specify a dictionary of class variables and methods. +.. cfunction:: PyObject* PyErr_NewExceptionWithDoc(char *name, const char *doc, PyObject *base) + + Creates and returns a new exception object. The *name* argument must be the + name of the new exception, a C string of the form ``module.class``. If *doc* + is non-*NULL*, it will be used to define the docstring for the exception. + If *base* is NULL, it creates a class object derived from :exc:`Exception` + (accessible in C as :cdata:`PyExc_Exception`). See :cfunc:`PyErr_NewException` + for more information about *name* and *base*. + + .. versionadded:: 2.7 + + .. cfunction:: void PyErr_WriteUnraisable(PyObject *obj) This utility function prints a warning message to ``sys.stderr`` when an Index: Doc/data/refcounts.dat =================================================================== --- Doc/data/refcounts.dat (revision 76959) +++ Doc/data/refcounts.dat (working copy) @@ -242,6 +242,11 @@ 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_NoMemory:PyObject*::null: PyErr_NormalizeException:void::: Index: Lib/test/test_exceptions.py =================================================================== --- Lib/test/test_exceptions.py (revision 76959) +++ Lib/test/test_exceptions.py (working copy) @@ -429,7 +429,32 @@ self.assertTrue(e is RuntimeError, e) self.assertTrue("maximum recursion depth exceeded" in str(v), v) + 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") + + error1 = _testcapi.make_exception_with_doc("_testcapi.error1") + self.assertIs(type(error1), type) + self.assertTrue(issubclass(error1, Exception)) + self.assertIsNone(error1.__doc__) + + error2 = _testcapi.make_exception_with_doc("_testcapi.error2", doc2) + self.assertEqual(error2.__doc__, doc2) + + error3 = _testcapi.make_exception_with_doc("_testcapi.error3", + base=error2) + self.assertTrue(issubclass(error3, error2)) + + error4 = _testcapi.make_exception_with_doc("_testcapi.error4", doc4, + error3) + self.assertTrue(issubclass(error4, error3)) + self.assertEqual(error4.__doc__, doc4) + + def test_main(): run_unittest(ExceptionTests) Index: Modules/_testcapimodule.c =================================================================== --- Modules/_testcapimodule.c (revision 76959) +++ Modules/_testcapimodule.c (working copy) @@ -1033,6 +1033,25 @@ 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) +{ + char *name; + const char *doc = NULL; + PyObject *base = NULL; + + static char *kwlist[] = {"name", "doc", "base", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "s|sO:make_exception_with_doc", kwlist, + &name, &doc, &base)) + return NULL; + + return PyErr_NewExceptionWithDoc(name, doc, base); +} + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, {"test_config", (PyCFunction)test_config, METH_NOARGS}, @@ -1081,6 +1100,8 @@ #endif {"traceback_print", traceback_print, METH_VARARGS}, {"code_newempty", code_newempty, METH_VARARGS}, + {"make_exception_with_doc", (PyCFunction)make_exception_with_doc, + METH_VARARGS | METH_KEYWORDS}, {NULL, NULL} /* sentinel */ };