cantera/include/cantera/cython/funcWrapper.h
Ray Speth 8e7588671f [Cython/Reactor] Print messages from exceptions raised in Python callbacks
Unlike the normal case for CallbackError, where the error message is visible
when it is re-raised in Python, errors occuring during reactor network
integration aren't necessarily terminal, so they can't simply be re-raised after
returning to Python. But we still want to display the information they contain,
so we need to extract that information from the Python exception objects in the
what() function that is called by cvodes_rhs.
2014-09-23 02:20:41 +00:00

113 lines
3.2 KiB
C++

#ifndef CT_CYTHON_FUNC_WRAPPER
#define CT_CYTHON_FUNC_WRAPPER
#include "cantera/numerics/Func1.h"
#include "cantera/base/ctexceptions.h"
#include <stdexcept>
#include "Python.h"
typedef double(*callback_wrapper)(double, void*, void**);
// A C++ exception that holds a Python exception so that it can be re-raised
// by translate_exception()
class CallbackError : public Cantera::CanteraError
{
public:
CallbackError(void* type, void* value) :
m_type((PyObject*) type),
m_value((PyObject*) value)
{
}
const char* what() const throw() {
formattedMessage_ = "\n" + std::string(71, '*') + "\n";
formattedMessage_ += "Exception raised in Python callback function:\n";
PyObject* name = PyObject_GetAttrString(m_type, "__name__");
PyObject* value_str = PyObject_Str(m_value);
#if PY_MAJOR_VERSION > 2
PyObject* name_bytes = PyUnicode_AsASCIIString(name);
PyObject* value_bytes = PyUnicode_AsASCIIString(value_str);
#else
PyObject* name_bytes = PyObject_Bytes(name);
PyObject* value_bytes = PyObject_Bytes(value_str);
#endif
if (name_bytes) {
formattedMessage_ += PyBytes_AsString(name_bytes);
Py_DECREF(name_bytes);
} else {
formattedMessage_ += "<error determining exception type>";
}
formattedMessage_ += ": ";
if (value_bytes) {
formattedMessage_ += PyBytes_AsString(value_bytes);
Py_DECREF(value_bytes);
} else {
formattedMessage_ += "<error determining exception message>";
}
Py_XDECREF(name);
Py_XDECREF(value_str);
formattedMessage_ += "\n" + std::string(71, '*') + "\n";
return formattedMessage_.c_str();
}
PyObject* m_type;
PyObject* m_value;
};
// A function of one variable implemented as a callable Python object
class Func1Py : public Cantera::Func1
{
public:
Func1Py(callback_wrapper callback, void* pyobj) :
m_callback(callback),
m_pyobj(pyobj) {
}
double eval(double t) const {
void* err[2] = {0, 0};
double y = m_callback(t, m_pyobj, err);
if (err[0]) {
throw CallbackError(err[0], err[1]);
}
return y;
}
private:
callback_wrapper m_callback;
void* m_pyobj;
};
// Translate C++ Exceptions generated by Cantera to appropriate Python
// exceptions. Used with Cython function declarations, e.g:
// cdef double eval(double) except +translate_exception
inline int translate_exception()
{
try {
if (!PyErr_Occurred()) {
// Let the latest Python exception pass through and ignore the
// current one.
throw;
}
} catch (const CallbackError& exn) {
// Re-raise a Python exception generated in a callback
PyErr_SetObject(exn.m_type, exn.m_value);
} catch (const std::out_of_range& exn) {
PyErr_SetString(PyExc_IndexError, exn.what());
} catch (const std::exception& exn) {
PyErr_SetString(PyExc_Exception, exn.what());
} catch (...) {
PyErr_SetString(PyExc_Exception, "Unknown exception");
}
return 0;
}
#endif