Library of Assembled Shared Sources
python/exception.h
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2025 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#ifndef LASS_GUARDIAN_OF_INCLUSION_PYTHON_EXCEPTION_H
44#define LASS_GUARDIAN_OF_INCLUSION_PYTHON_EXCEPTION_H
45
46#include "python_common.h"
47#include "pyobject_ptr.h"
48
49namespace lass
50{
51namespace python
52{
53
54
55/** @defgroup PythonExceptions Python Exceptions
56 * @brief Handling Python exceptions in C++, and translating C++ exceptions to Python.
57 *
58 * This module provides utilities for converting C++ exceptions into Python exceptions,
59 * and for handling Python exceptions in C++ code.
60 *
61 * Its main part is the class `PythonException` that represents a Python exception in C++ code.
62 * Additionally, it provides functions and macros to facilitate raising and handling Python exceptions.
63 *
64 * @ingroup Python
65 */
66
67
68/** C++ exception type that holds a Python exception
69 *
70 * This class is used to propagate Python exceptions through C++ code. Can be used to both
71 * - handle exceptions originating from Python,
72 * - raise new Python exceptions from C++ code.
73 *
74 * @ingroup PythonExceptions
75 */
76class PythonException: public util::ExceptionMixin<PythonException>
77{
78public:
79 LASS_PYTHON_DLL PythonException(const TPyObjPtr& type, const TPyObjPtr& value, const TPyObjPtr& traceback, std::string loc);
80 LASS_PYTHON_DLL PythonException(PyObject* type, const std::string& msg, std::string loc);
81 LASS_PYTHON_DLL PythonException(PyObject* type, const std::string& msg);
82
83 ~PythonException() noexcept {}
84 const python::TPyObjPtr& type() const { return type_; }
85 const python::TPyObjPtr& value() const { return value_; }
86 const python::TPyObjPtr& traceback() const { return traceback_; }
87private:
88 static LASS_PYTHON_DLL const std::string LASS_CALL extractMessage(PyObject* type, PyObject* value = 0);
89
91 python::TPyObjPtr value_;
92 python::TPyObjPtr traceback_;
93};
94
95
96
97/** Raise an explicitly chained Python exception
98 *
99 * Like `PyErr_Format`, but chains the new exception to the current one if any is set.
100 * More explicitly, if a Python exception is already set, that is if `PyErr_Occurred()` returns true,
101 * then the current exception is fetched and used as the `__cause__` and `__context__` of the new exception.
102 *
103 * Roughly equivalent to:
104 * ```
105 * except Exception as err:
106 * raise exception(format % vargs) from err
107 * ```
108 *
109 * For more information on exception chaining, see:
110 * https://docs.python.org/3/library/exceptions.html#exception-context
111 *
112 * If no exception is currently set, this function behaves like `PyErr_Format`.
113 *
114 * @param exception The Python exception type to raise, e.g. `PyExc_TypeError`.
115 * @param format,... The format string and arguments, like in `PyErr_Format`.
116 * They do **not** behave like `printf`, but they have the same meaning as in [PyUnicode_FromFormat].
117 *
118 * @return Always returns `nullptr`, so that it can be used directly in return statements.
119 *
120 * @ingroup PythonExceptions
121 *
122 * [PyUnicode_FromFormat]: https://docs.python.org/3/c-api/unicode.html#c.PyUnicode_FromFormat
123 */
124LASS_PYTHON_DLL PyObject* LASS_CALL chainErrFormat(PyObject* exception, const char* format, ...);
125
126
127
128/** Raise an explicitly chained Python exception
129 *
130 * Like chainErrFormat(), but takes a `va_list` instead of variable arguments.
131 * @ingroup PythonExceptions
132 */
133LASS_PYTHON_DLL PyObject* LASS_CALL chainErrFormatV(PyObject* exception, const char* format, va_list vargs);
134
135
136
137namespace impl
138{
139 /** Prepend a message to the current Python exception value
140 *
141 * This function adds a custom header to the current Python exception message,
142 * if one is set. This can be useful for providing additional context about
143 * the error.
144 *
145 * If no exception is currently set, i.e. `PyErr_Occurred()` returns false, then
146 * this function does nothing.
147 *
148 * @param header The header to add to the exception message.
149 * @note Only works for exceptions whose value is a string. If the value is not a string,
150 * the function does nothing.
151 * @ingroup PythonExceptions
152 */
153 LASS_PYTHON_DLL void LASS_CALL addMessageHeader(const char* header);
154
155
156
157 /** Fetch the current Python exception and throw it as a C++ PythonException
158 *
159 * @ingroup PythonExceptions
160 */
161 LASS_PYTHON_DLL void LASS_CALL fetchAndThrowPythonException [[noreturn]] (std::string loc = "");
162
163
164
165 /** Handle a C++ exception by raising an Python exception
166 *
167 * This function translates a C++ exception into a Python exception.
168 * It supports `PythonException`, `util::Exception`, and standard C++ exceptions.
169 * It also supports `std::filesystem::filesystem_error` and `std::system_error`
170 * by translating the error code to an appropriate OSError subclass.
171 *
172 * If the exception type is not recognized, it will be translated to a generic
173 * `Exception` with the what() message of the C++ exception.
174 *
175 * @param error The exception pointer to handle. This is typically obtained
176 * using `std::current_exception()`.
177 *
178 * @ingroup PythonExceptions
179 */
180 LASS_PYTHON_DLL void LASS_CALL handleException(std::exception_ptr error);
181
182
183
184 /** Raiser type for enforcers that raises a Python exception
185 *
186 * This can be used as a building block for Python-related enforcers.
187 * It's used by the macros `PY_ENFORCE_POINTER()`, `PY_ENFORCE_ZERO()`, and `PY_ENFORCE_NOTZERO()`.
188 *
189 * If a Python error is set, it will be fetched and thrown as a `PythonException`.
190 * Otherwise, a new `PythonException` of type `AssertionError` will be thrown.
191 *
192 * @throws ::lass::python::PythonException
193 * @ingroup PythonExceptions Enforcers
194 */
196 {
197 template <typename T, typename C>
198 static void raise(const T&, const C&, const std::string& message, const char* locus)
199 {
200 LockGIL LASS_UNUSED(lock);
201 if (PyErr_Occurred())
202 {
204 }
205 if (!message.empty())
206 {
207 throw PythonException(PyExc_AssertionError, message, locus);
208 }
209 throw PythonException(PyExc_AssertionError, locus, locus);
210 }
211 };
212}
213}
214}
215
216
217#define LASS_PYTHON_CATCH_AND_RETURN_EX(v_errorReturnValue)\
218 catch (const ::std::exception&)\
219 {\
220 ::lass::python::impl::handleException(std::current_exception());\
221 return v_errorReturnValue;\
222 }
223
224#define LASS_PYTHON_CATCH_AND_RETURN\
225 LASS_PYTHON_CATCH_AND_RETURN_EX(0)
226
227
228
229/** Enforce that a Python pointer is not null
230 *
231 * @throws ::lass::python::PythonException
232 * @sa ::lass::python::impl::PythonFetchRaiser
233 * @ingroup PythonExceptions
234 * @sa Enforcers
235 */
236#define PY_ENFORCE_POINTER(pointer)\
237 *LASS_UTIL_IMPL_MAKE_ENFORCER(\
238 ::lass::util::impl::TruePredicate,\
239 ::lass::python::impl::PythonFetchRaiser,\
240 (pointer), \
241 int(0), \
242 "'" LASS_STRINGIFY(pointer) "' in " LASS_HERE)
243
244
245
246/** Enforce that an expression evaluates to zero
247 *
248 * @throws ::lass::python::PythonException
249 * @sa ::lass::python::impl::PythonFetchRaiser
250 * @ingroup PythonExceptions
251 * @sa Enforcers
252 */
253#define PY_ENFORCE_ZERO(expression)\
254 *LASS_UTIL_IMPL_MAKE_ENFORCER(\
255 ::lass::util::impl::EqualPredicate,\
256 ::lass::python::impl::PythonFetchRaiser,\
257 (expression), \
258 int(0), \
259 "'" LASS_STRINGIFY(expression) "' in " LASS_HERE)
260
261
262
263/** Enforce that an expression evaluates to not zero
264 *
265 * @throws ::lass::python::PythonException
266 * @sa ::lass::python::impl::PythonFetchRaiser
267 * @ingroup PythonExceptions
268 * @sa Enforcers
269 */
270#define PY_ENFORCE_NOTZERO(expression)\
271 *LASS_UTIL_IMPL_MAKE_ENFORCER(\
272 ::lass::util::impl::UnequalPredicate,\
273 ::lass::python::impl::PythonFetchRaiser,\
274 (expression), \
275 int(0), \
276 "'" LASS_STRINGIFY(expression) "' in " LASS_HERE)
277
278#endif
acquire the GIL for the current scope.
Definition gil.h:56
C++ exception type that holds a Python exception.
void addMessageHeader(const char *header)
Prepend a message to the current Python exception value.
void handleException(std::exception_ptr ptr)
Handle a C++ exception by raising an Python exception.
void fetchAndThrowPythonException(std::string loc)
Fetch the current Python exception and throw it as a C++ PythonException.
PyObject * chainErrFormat(PyObject *exception, const char *format,...)
Raise an explicitly chained Python exception.
PyObject * chainErrFormatV(PyObject *exception, const char *format, va_list vargs)
Raise an explicitly chained Python exception.
PyObjectPtr< PyObject >::Type TPyObjPtr
PyObjectPtr to a PyObject.
Comprehensive C++ to Python binding library.
Library for Assembled Shared Sources.
Definition config.h:53
Raiser type for enforcers that raises a Python exception.