Cantera  3.1.0a1
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 
12 typedef 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).
16 class PyFuncInfo {
17 public:
18  PyFuncInfo()
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 
64 private:
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()
73 class CallbackError : public Cantera::CanteraError
74 {
75 public:
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 
98  ~CallbackError() {
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
143 class Func1Py : public Cantera::Func1
144 {
145 public:
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 
160 private:
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
167 extern PyObject* pyCanteraError;
168 #else
169 extern "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.
186 template <class ... Args>
187 std::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.
199 template <class ... Args>
200 std::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
215 inline 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
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:66
const char * what() const override
Get a description of the error.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
CanteraError(const string &procedure, const string &msg, const Args &... args)
Normal Constructor for the CanteraError base class.
Definition: ctexceptions.h:83
virtual string getClass() const
Method overridden by derived classes to indicate their type.
Definition: ctexceptions.h:106
Base class for 'functor' classes that evaluate a function of one variable.
Definition: Func1.h:75
virtual double eval(double t) const
Evaluate the function.
Definition: Func1.cpp:28
An error indicating that an unimplemented function has been called.
Definition: ctexceptions.h:195
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...