Cantera  4.0.0a2
Loading...
Searching...
No Matches
funcWrapper.h
1// This file is part of Cantera. See License.txt in the top-level directory or
2// at https://cantera.org/license.txt for license and copyright information.
3
4#ifndef CT_CYTHON_FUNC_WRAPPER
5#define CT_CYTHON_FUNC_WRAPPER
6
9#include <stdexcept>
10#include "Python.h"
11
12typedef double(*callback_wrapper)(double, void*, void**);
13
14//! A class to hold information needed to call Python functions from delegated
15//! methods (see class Delegator).
17public:
19 : m_func(nullptr)
20 , m_exception_type(nullptr)
21 , m_exception_value(nullptr)
22 {
23 }
24
25 PyFuncInfo(const PyFuncInfo& other)
26 : m_func(other.m_func)
27 , m_exception_type(other.m_exception_type)
28 , m_exception_value(other.m_exception_value)
29 {
30 Py_XINCREF(m_exception_type);
31 Py_XINCREF(m_exception_value);
32 }
33
34 ~PyFuncInfo() {
35 Py_XDECREF(m_exception_type);
36 Py_XDECREF(m_exception_value);
37 }
38
39 PyObject* func() {
40 return m_func;
41 }
42 void setFunc(PyObject* f) {
43 m_func = f;
44 }
45
46 PyObject* exceptionType() {
47 return m_exception_type;
48 }
49 void setExceptionType(PyObject* obj) {
50 Py_XDECREF(m_exception_type);
51 Py_XINCREF(obj);
52 m_exception_type = obj;
53 }
54
55 PyObject* exceptionValue() {
56 return m_exception_value;
57 }
58 void setExceptionValue(PyObject* obj) {
59 Py_XDECREF(m_exception_value);
60 Py_XINCREF(obj);
61 m_exception_value = obj;
62 }
63
64private:
65 PyObject* m_func;
66 PyObject* m_exception_type;
67 PyObject* m_exception_value;
68};
69
70
71// A C++ exception that holds a Python exception so that it can be re-raised
72// by translate_exception()
74{
75public:
76 //! Constructor used by Func1Py
77 CallbackError(void* type, void* value) :
78 CanteraError("Python callback function"),
79 m_type((PyObject*) type),
80 m_value((PyObject*) value)
81 {
82 Py_XINCREF(m_type);
83 Py_XINCREF(m_value);
84 }
85
86 //! Constructor used by pyOverride()
87 explicit CallbackError(PyFuncInfo& info) :
88 CanteraError("Python callback function"),
89 m_type(info.exceptionType()),
90 m_value(info.exceptionValue())
91 {
92 Py_XINCREF(m_type);
93 Py_XINCREF(m_value);
94 info.setExceptionType(0);
95 info.setExceptionValue(0);
96 }
97
99 Py_XDECREF(m_type);
100 Py_XDECREF(m_value);
101 }
102
103 std::string getMessage() const override {
104 std::string msg;
105
106 PyObject* name = PyObject_GetAttrString(m_type, "__name__");
107 PyObject* value_str = PyObject_Str(m_value);
108
109 PyObject* name_bytes = PyUnicode_AsASCIIString(name);
110 PyObject* value_bytes = PyUnicode_AsASCIIString(value_str);
111
112 if (name_bytes) {
113 msg += PyBytes_AsString(name_bytes);
114 Py_DECREF(name_bytes);
115 } else {
116 msg += "<error determining exception type>";
117 }
118
119 msg += ": ";
120
121 if (value_bytes) {
122 msg += PyBytes_AsString(value_bytes);
123 Py_DECREF(value_bytes);
124 } else {
125 msg += "<error determining exception message>";
126 }
127
128 Py_XDECREF(name);
129 Py_XDECREF(value_str);
130 return msg;
131 }
132
133 std::string getClass() const override {
134 return "Exception";
135 }
136
137 PyObject* m_type;
138 PyObject* m_value;
139};
140
141
142// A function of one variable implemented as a callable Python object
144{
145public:
146 Func1Py(callback_wrapper callback, void* pyobj) :
147 m_callback(callback),
148 m_pyobj(pyobj) {
149 }
150
151 double eval(double t) const override {
152 void* err[2] = {0, 0};
153 double y = m_callback(t, m_pyobj, err);
154 if (err[0]) {
155 throw CallbackError(err[0], err[1]);
156 }
157 return y;
158 }
159
160private:
161 callback_wrapper m_callback;
162 void* m_pyobj;
163};
164
165//! Return the Python ``CanteraError`` class, used by translate_exception() to raise a
166//! ``CanteraError`` from C++ code.
167//!
168//! The class object is fetched on first use from its single canonical definition in the
169//! ``cantera._utils`` module and cached. Resolving it this way keeps each Python
170// extension module self-contained.
171inline PyObject* getCanteraErrorClass() {
172 // Cached per extension module; CanteraError is a singleton class object, so the
173 // borrowed reference held here remains valid for the lifetime of the module.
174 static PyObject* cls = nullptr;
175 if (cls == nullptr) {
176 PyObject* mod = PyImport_ImportModule("cantera._utils");
177 if (mod != nullptr) {
178 cls = PyObject_GetAttrString(mod, "CanteraError");
179 Py_DECREF(mod);
180 }
181 if (cls == nullptr) {
182 // Fall back to RuntimeError (the base class of CanteraError) if the lookup
183 // fails, ensuring a valid exception type is always returned.
184 PyErr_Clear();
185 cls = PyExc_RuntimeError;
186 }
187 }
188 return cls;
189}
190
191//! Take a function which requires Python function information (as a PyFuncInfo
192//! object) and capture that object to generate a function that does not require
193//! any Python-specific arguments.
194//!
195//! The inner function is responsible for catching Python exceptions and
196//! stashing their details in the PyFuncInfo object. The wrapper function
197//! generated here examines the stashed exception and throws a C++ exception
198//!
199//! The caller of pyOverride must continue holding a reference to pyFunc for the
200//! lifetime of the pyOverride object to prevent it from being prematurely garbage
201//! collected.
202template <class ... Args>
203std::function<void(Args ...)> pyOverride(PyObject* pyFunc, void func(PyFuncInfo&, Args ... args)) {
204 PyFuncInfo func_info;
205 func_info.setFunc(pyFunc);
206 return [func_info, func](Args ... args) mutable {
207 func(func_info, args ...);
208 if (func_info.exceptionType()) {
209 throw CallbackError(func_info);
210 }
211 };
212}
213
214//! Same as above, but for functions that return an int.
215template <class ... Args>
216std::function<int(Args ...)> pyOverride(PyObject* pyFunc, int func(PyFuncInfo&, Args ... args)) {
217 PyFuncInfo func_info;
218 func_info.setFunc(pyFunc);
219 return [func_info, func](Args ... args) mutable {
220 int ret = func(func_info, args ...);
221 if (func_info.exceptionType()) {
222 throw CallbackError(func_info);
223 }
224 return ret;
225 };
226}
227
228// Translate C++ Exceptions generated by Cantera to appropriate Python
229// exceptions. Used with Cython function declarations, e.g:
230// cdef double eval(double) except +translate_exception
231inline int translate_exception()
232{
233 try {
234 if (!PyErr_Occurred()) {
235 // Let the latest Python exception pass through and ignore the
236 // current one.
237 throw;
238 }
239 } catch (const CallbackError& exn) {
240 // Re-raise a Python exception generated in a callback
241 PyErr_SetObject(exn.m_type, exn.m_value);
242 } catch (const std::out_of_range& exn) {
243 PyErr_SetString(PyExc_IndexError, exn.what());
244 } catch (const Cantera::NotImplementedError& exn) {
245 PyErr_SetString(PyExc_NotImplementedError, exn.what());
246 } catch (const Cantera::ArraySizeError& exn) {
247 PyErr_SetString(PyExc_ValueError, exn.what());
248 } catch (const Cantera::CanteraError& exn) {
249 PyErr_SetString(getCanteraErrorClass(), exn.what());
250 } catch (const std::exception& exn) {
251 PyErr_SetString(PyExc_RuntimeError, exn.what());
252 } catch (...) {
253 PyErr_SetString(PyExc_Exception, "Unknown exception");
254 }
255 return 0;
256}
257
258#endif
std::string getClass() const override
Method overridden by derived classes to indicate their type.
CallbackError(PyFuncInfo &info)
Constructor used by pyOverride()
Definition funcWrapper.h:87
std::string getMessage() const override
Method overridden by derived classes to format the error message.
CallbackError(void *type, void *value)
Constructor used by Func1Py.
Definition funcWrapper.h:77
Array size error.
Base class for exceptions thrown by Cantera classes.
const char * what() const override
Get a description of the error.
CanteraError()
Protected default constructor discourages throwing errors containing no information.
Base class for 'functor' classes that evaluate a function of one variable.
Definition Func1.h:75
An error indicating that an unimplemented function has been called.
double eval(double t) const override
Evaluate the function.
A class to hold information needed to call Python functions from delegated methods (see class Delegat...
Definition funcWrapper.h:16
Definitions for the classes that are thrown when Cantera experiences an error condition (also contain...