Cantera  3.3.0a1
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
165extern PyObject* pyCanteraError;
166
167//! Take a function which requires Python function information (as a PyFuncInfo
168//! object) and capture that object to generate a function that does not require
169//! any Python-specific arguments.
170//!
171//! The inner function is responsible for catching Python exceptions and
172//! stashing their details in the PyFuncInfo object. The wrapper function
173//! generated here examines the stashed exception and throws a C++ exception
174//!
175//! The caller of pyOverride must continue holding a reference to pyFunc for the
176//! lifetime of the pyOverride object to prevent it from being prematurely garbage
177//! collected.
178template <class ... Args>
179std::function<void(Args ...)> pyOverride(PyObject* pyFunc, void func(PyFuncInfo&, Args ... args)) {
180 PyFuncInfo func_info;
181 func_info.setFunc(pyFunc);
182 return [func_info, func](Args ... args) mutable {
183 func(func_info, args ...);
184 if (func_info.exceptionType()) {
185 throw CallbackError(func_info);
186 }
187 };
188}
189
190//! Same as above, but for functions that return an int.
191template <class ... Args>
192std::function<int(Args ...)> pyOverride(PyObject* pyFunc, int func(PyFuncInfo&, Args ... args)) {
193 PyFuncInfo func_info;
194 func_info.setFunc(pyFunc);
195 return [func_info, func](Args ... args) mutable {
196 int ret = func(func_info, args ...);
197 if (func_info.exceptionType()) {
198 throw CallbackError(func_info);
199 }
200 return ret;
201 };
202}
203
204// Translate C++ Exceptions generated by Cantera to appropriate Python
205// exceptions. Used with Cython function declarations, e.g:
206// cdef double eval(double) except +translate_exception
207inline int translate_exception()
208{
209 try {
210 if (!PyErr_Occurred()) {
211 // Let the latest Python exception pass through and ignore the
212 // current one.
213 throw;
214 }
215 } catch (const CallbackError& exn) {
216 // Re-raise a Python exception generated in a callback
217 PyErr_SetObject(exn.m_type, exn.m_value);
218 } catch (const std::out_of_range& exn) {
219 PyErr_SetString(PyExc_IndexError, exn.what());
220 } catch (const Cantera::NotImplementedError& exn) {
221 PyErr_SetString(PyExc_NotImplementedError, exn.what());
222 } catch (const Cantera::CanteraError& exn) {
223 PyErr_SetString(pyCanteraError, exn.what());
224 } catch (const std::exception& exn) {
225 PyErr_SetString(PyExc_RuntimeError, exn.what());
226 } catch (...) {
227 PyErr_SetString(PyExc_Exception, "Unknown exception");
228 }
229 return 0;
230}
231
232#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
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...