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