Cantera 2.6.0
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
11#define CANTERA_USE_INTERNAL
13#include "Python.h"
14
15typedef double(*callback_wrapper)(double, void*, void**);
16
17//! A class to hold information needed to call Python functions from delgated
18//! methods (see class Delegator).
20public:
22 : m_func(nullptr)
23 , m_exception_type(nullptr)
24 , m_exception_value(nullptr)
25 {
26 }
27
28 PyFuncInfo(const PyFuncInfo& other)
29 : m_func(other.m_func)
30 , m_exception_type(other.m_exception_type)
31 , m_exception_value(other.m_exception_value)
32 {
33 Py_XINCREF(m_exception_type);
34 Py_XINCREF(m_exception_value);
35 }
36
37 ~PyFuncInfo() {
38 Py_XDECREF(m_exception_type);
39 Py_XDECREF(m_exception_value);
40 }
41
42 PyObject* func() {
43 return m_func;
44 }
45 void setFunc(PyObject* f) {
46 m_func = f;
47 }
48
49 PyObject* exceptionType() {
50 return m_exception_type;
51 }
52 void setExceptionType(PyObject* obj) {
53 Py_XDECREF(m_exception_type);
54 Py_XINCREF(obj);
55 m_exception_type = obj;
56 }
57
58 PyObject* exceptionValue() {
59 return m_exception_value;
60 }
61 void setExceptionValue(PyObject* obj) {
62 Py_XDECREF(m_exception_value);
63 Py_XINCREF(obj);
64 m_exception_value = obj;
65 }
66
67private:
68 PyObject* m_func;
69 PyObject* m_exception_type;
70 PyObject* m_exception_value;
71};
72
73
74// A C++ exception that holds a Python exception so that it can be re-raised
75// by translate_exception()
76class CallbackError : public Cantera::CanteraError
77{
78public:
79 //! Constructor used by Func1Py
80 CallbackError(void* type, void* value) :
81 CanteraError("Python callback function"),
82 m_type((PyObject*) type),
83 m_value((PyObject*) value)
84 {
85 Py_XINCREF(m_type);
86 Py_XINCREF(m_value);
87 }
88
89 //! Constructor used by pyOverride()
90 explicit CallbackError(PyFuncInfo& info) :
91 CanteraError("Python callback function"),
92 m_type(info.exceptionType()),
93 m_value(info.exceptionValue())
94 {
95 Py_XINCREF(m_type);
96 Py_XINCREF(m_value);
97 info.setExceptionType(0);
98 info.setExceptionValue(0);
99 }
100
101 ~CallbackError() {
102 Py_XDECREF(m_type);
103 Py_XDECREF(m_value);
104 }
105
106 std::string getMessage() const {
107 std::string msg;
108
109 PyObject* name = PyObject_GetAttrString(m_type, "__name__");
110 PyObject* value_str = PyObject_Str(m_value);
111
112 PyObject* name_bytes = PyUnicode_AsASCIIString(name);
113 PyObject* value_bytes = PyUnicode_AsASCIIString(value_str);
114
115 if (name_bytes) {
116 msg += PyBytes_AsString(name_bytes);
117 Py_DECREF(name_bytes);
118 } else {
119 msg += "<error determining exception type>";
120 }
121
122 msg += ": ";
123
124 if (value_bytes) {
125 msg += PyBytes_AsString(value_bytes);
126 Py_DECREF(value_bytes);
127 } else {
128 msg += "<error determining exception message>";
129 }
130
131 Py_XDECREF(name);
132 Py_XDECREF(value_str);
133 return msg;
134 }
135
136 virtual std::string getClass() const {
137 return "Exception";
138 }
139
140 PyObject* m_type;
141 PyObject* m_value;
142};
143
144
145// A function of one variable implemented as a callable Python object
146class Func1Py : public Cantera::Func1
147{
148public:
149 Func1Py(callback_wrapper callback, void* pyobj) :
150 m_callback(callback),
151 m_pyobj(pyobj) {
152 }
153
154 double eval(double t) const {
155 void* err[2] = {0, 0};
156 double y = m_callback(t, m_pyobj, err);
157 if (err[0]) {
158 throw CallbackError(err[0], err[1]);
159 }
160 return y;
161 }
162
163private:
164 callback_wrapper m_callback;
165 void* m_pyobj;
166};
167
168extern "C" {
169 CANTERA_CAPI PyObject* pyCanteraError;
170}
171
172
173
174//! Take a function which requires Python function information (as a PyFuncInfo
175//! object) and capture that object to generate a function that does not reqire
176//! any Python-specific arguments.
177//!
178//! The inner function is responsible for catching Python exceptions and
179//! stashing their details in the PyFuncInfo object. The wrapper function
180//! generated here examines the stashed exception and throws a C++ exception
181//!
182//! The caller of pyOverride must continue holding a reference to pyFunc for the
183//! lifetime of the pyOverride object to prevent it from being prematurely garbage
184//! collected.
185template <class ... Args>
186std::function<void(Args ...)> pyOverride(PyObject* pyFunc, void func(PyFuncInfo&, Args ... args)) {
187 PyFuncInfo func_info;
188 func_info.setFunc(pyFunc);
189 return [func_info, func](Args ... args) mutable {
190 func(func_info, args ...);
191 if (func_info.exceptionType()) {
192 throw CallbackError(func_info);
193 }
194 };
195}
196
197//! Same as above, but for functions that return an int.
198template <class ... Args>
199std::function<int(Args ...)> pyOverride(PyObject* pyFunc, int func(PyFuncInfo&, Args ... args)) {
200 PyFuncInfo func_info;
201 func_info.setFunc(pyFunc);
202 return [func_info, func](Args ... args) mutable {
203 int ret = func(func_info, args ...);
204 if (func_info.exceptionType()) {
205 throw CallbackError(func_info);
206 }
207 return ret;
208 };
209}
210
211// Translate C++ Exceptions generated by Cantera to appropriate Python
212// exceptions. Used with Cython function declarations, e.g:
213// cdef double eval(double) except +translate_exception
214inline int translate_exception()
215{
216 try {
217 if (!PyErr_Occurred()) {
218 // Let the latest Python exception pass through and ignore the
219 // current one.
220 throw;
221 }
222 } catch (const CallbackError& exn) {
223 // Re-raise a Python exception generated in a callback
224 PyErr_SetObject(exn.m_type, exn.m_value);
225 } catch (const std::out_of_range& exn) {
226 PyErr_SetString(PyExc_IndexError, exn.what());
227 } catch (const Cantera::CanteraError& exn) {
228 PyErr_SetString(pyCanteraError, exn.what());
229 } catch (const std::exception& exn) {
230 PyErr_SetString(PyExc_RuntimeError, exn.what());
231 } catch (...) {
232 PyErr_SetString(PyExc_Exception, "Unknown exception");
233 }
234 return 0;
235}
236
237#endif
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:61
virtual std::string getMessage() const
Method overridden by derived classes to format the error message.
virtual std::string getClass() const
Method overridden by derived classes to indicate their type.
Definition: ctexceptions.h:99
const char * what() const
Get a description of the error.
CanteraError(const std::string &procedure, const std::string &msg, const Args &... args)
Normal Constructor for the CanteraError base class.
Definition: ctexceptions.h:78
Base class for 'functor' classes that evaluate a function of one variable.
Definition: Func1.h:44
virtual doublereal eval(doublereal t) const
Evaluate the function.
Definition: Func1.cpp:60
A class to hold information needed to call Python functions from delgated methods (see class Delegato...
Definition: funcWrapper.h:19
Definitions for the classes that are thrown when Cantera experiences an error condition (also contain...