mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
Merge pull request #273 from lsst-dm/master
Add support for user defined exception translators
This commit is contained in:
commit
3c6ada3a48
@ -828,6 +828,8 @@ In other words, :func:`init` creates an anonymous function that invokes an
|
|||||||
in-place constructor. Memory allocation etc. is already take care of beforehand
|
in-place constructor. Memory allocation etc. is already take care of beforehand
|
||||||
within pybind11.
|
within pybind11.
|
||||||
|
|
||||||
|
.. _catching_and_throwing_exceptions:
|
||||||
|
|
||||||
Catching and throwing exceptions
|
Catching and throwing exceptions
|
||||||
================================
|
================================
|
||||||
|
|
||||||
@ -880,6 +882,65 @@ There is also a special exception :class:`cast_error` that is thrown by
|
|||||||
:func:`handle::call` when the input arguments cannot be converted to Python
|
:func:`handle::call` when the input arguments cannot be converted to Python
|
||||||
objects.
|
objects.
|
||||||
|
|
||||||
|
Registering custom exception translators
|
||||||
|
========================================
|
||||||
|
|
||||||
|
If the default exception conversion policy described
|
||||||
|
:ref:`above <catching_and_throwing_exceptions>`
|
||||||
|
is insufficient, pybind11 also provides support for registering custom
|
||||||
|
exception translators.
|
||||||
|
|
||||||
|
The function ``register_exception_translator(translator)`` takes a stateless
|
||||||
|
callable (e.g. a function pointer or a lambda function without captured
|
||||||
|
variables) with the following call signature: ``void(std::exception_ptr)``.
|
||||||
|
|
||||||
|
When a C++ exception is thrown, registered exception translators are tried
|
||||||
|
in reverse order of registration (i.e. the last registered translator gets
|
||||||
|
a first shot at handling the exception).
|
||||||
|
|
||||||
|
Inside the translator, ``std::rethrow_exception`` should be used within
|
||||||
|
a try block to re-throw the exception. A catch clause can then use
|
||||||
|
``PyErr_SetString`` to set a Python exception as demonstrated
|
||||||
|
in :file:`example19.cpp``.
|
||||||
|
|
||||||
|
This example also demonstrates how to create custom exception types
|
||||||
|
with ``py::exception``.
|
||||||
|
|
||||||
|
The following example demonstrates this for a hypothetical exception class
|
||||||
|
``MyCustomException``:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
py::register_exception_translator([](std::exception_ptr p) {
|
||||||
|
try {
|
||||||
|
if (p) std::rethrow_exception(p);
|
||||||
|
} catch (const MyCustomException &e) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, e.what());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Multiple exceptions can be handled by a single translator. If the exception is
|
||||||
|
not caught by the current translator, the previously registered one gets a
|
||||||
|
chance.
|
||||||
|
|
||||||
|
If none of the registered exception translators is able to handle the
|
||||||
|
exception, it is handled by the default converter as described in the previous
|
||||||
|
section.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You must either call ``PyErr_SetString`` for every exception caught in a
|
||||||
|
custom exception translator. Failure to do so will cause Python to crash
|
||||||
|
with ``SystemError: error return without exception set``.
|
||||||
|
|
||||||
|
Exceptions that you do not plan to handle should simply not be caught.
|
||||||
|
|
||||||
|
You may also choose to explicity (re-)throw the exception to delegate it to
|
||||||
|
the other existing exception translators.
|
||||||
|
|
||||||
|
The ``py::exception`` wrapper for creating custom exceptions cannot (yet)
|
||||||
|
be used as a ``py::base``.
|
||||||
|
|
||||||
.. _opaque:
|
.. _opaque:
|
||||||
|
|
||||||
Treating STL data structures as opaque objects
|
Treating STL data structures as opaque objects
|
||||||
|
@ -25,6 +25,7 @@ set(PYBIND11_EXAMPLES
|
|||||||
example16.cpp
|
example16.cpp
|
||||||
example17.cpp
|
example17.cpp
|
||||||
example18.cpp
|
example18.cpp
|
||||||
|
example19.cpp
|
||||||
issues.cpp
|
issues.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ void init_ex15(py::module &);
|
|||||||
void init_ex16(py::module &);
|
void init_ex16(py::module &);
|
||||||
void init_ex17(py::module &);
|
void init_ex17(py::module &);
|
||||||
void init_ex18(py::module &);
|
void init_ex18(py::module &);
|
||||||
|
void init_ex19(py::module &);
|
||||||
void init_issues(py::module &);
|
void init_issues(py::module &);
|
||||||
|
|
||||||
#if defined(PYBIND11_TEST_EIGEN)
|
#if defined(PYBIND11_TEST_EIGEN)
|
||||||
@ -54,6 +55,7 @@ PYBIND11_PLUGIN(example) {
|
|||||||
init_ex16(m);
|
init_ex16(m);
|
||||||
init_ex17(m);
|
init_ex17(m);
|
||||||
init_ex18(m);
|
init_ex18(m);
|
||||||
|
init_ex19(m);
|
||||||
init_issues(m);
|
init_issues(m);
|
||||||
|
|
||||||
#if defined(PYBIND11_TEST_EIGEN)
|
#if defined(PYBIND11_TEST_EIGEN)
|
||||||
|
108
example/example19.cpp
Normal file
108
example/example19.cpp
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
example/example19.cpp -- exception translation
|
||||||
|
|
||||||
|
Copyright (c) 2016 Pim Schellart <P.Schellart@princeton.edu>
|
||||||
|
|
||||||
|
All rights reserved. Use of this source code is governed by a
|
||||||
|
BSD-style license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "example.h"
|
||||||
|
|
||||||
|
// A type that should be raised as an exeption in Python
|
||||||
|
class MyException : public std::exception {
|
||||||
|
public:
|
||||||
|
explicit MyException(const char * m) : message{m} {}
|
||||||
|
virtual const char * what() const noexcept override {return message.c_str();}
|
||||||
|
private:
|
||||||
|
std::string message = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// A type that should be translated to a standard Python exception
|
||||||
|
class MyException2 : public std::exception {
|
||||||
|
public:
|
||||||
|
explicit MyException2(const char * m) : message{m} {}
|
||||||
|
virtual const char * what() const noexcept override {return message.c_str();}
|
||||||
|
private:
|
||||||
|
std::string message = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// A type that is not derived from std::exception (and is thus unknown)
|
||||||
|
class MyException3 {
|
||||||
|
public:
|
||||||
|
explicit MyException3(const char * m) : message{m} {}
|
||||||
|
virtual const char * what() const noexcept {return message.c_str();}
|
||||||
|
private:
|
||||||
|
std::string message = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// A type that should be translated to MyException
|
||||||
|
// and delegated to its exception translator
|
||||||
|
class MyException4 : public std::exception {
|
||||||
|
public:
|
||||||
|
explicit MyException4(const char * m) : message{m} {}
|
||||||
|
virtual const char * what() const noexcept override {return message.c_str();}
|
||||||
|
private:
|
||||||
|
std::string message = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
void throws1() {
|
||||||
|
throw MyException("this error should go to a custom type");
|
||||||
|
}
|
||||||
|
|
||||||
|
void throws2() {
|
||||||
|
throw MyException2("this error should go to a standard Python exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
void throws3() {
|
||||||
|
throw MyException3("this error cannot be translated");
|
||||||
|
}
|
||||||
|
|
||||||
|
void throws4() {
|
||||||
|
throw MyException4("this error is rethrown");
|
||||||
|
}
|
||||||
|
|
||||||
|
void throws_logic_error() {
|
||||||
|
throw std::logic_error("this error should fall through to the standard handler");
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_ex19(py::module &m) {
|
||||||
|
// make a new custom exception and use it as a translation target
|
||||||
|
static py::exception<MyException> ex(m, "MyException");
|
||||||
|
py::register_exception_translator([](std::exception_ptr p) {
|
||||||
|
try {
|
||||||
|
if (p) std::rethrow_exception(p);
|
||||||
|
} catch (const MyException &e) {
|
||||||
|
PyErr_SetString(ex.ptr(), e.what());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// register new translator for MyException2
|
||||||
|
// no need to store anything here because this type will
|
||||||
|
// never by visible from Python
|
||||||
|
py::register_exception_translator([](std::exception_ptr p) {
|
||||||
|
try {
|
||||||
|
if (p) std::rethrow_exception(p);
|
||||||
|
} catch (const MyException2 &e) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, e.what());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// register new translator for MyException4
|
||||||
|
// which will catch it and delegate to the previously registered
|
||||||
|
// translator for MyException by throwing a new exception
|
||||||
|
py::register_exception_translator([](std::exception_ptr p) {
|
||||||
|
try {
|
||||||
|
if (p) std::rethrow_exception(p);
|
||||||
|
} catch (const MyException4 &e) {
|
||||||
|
throw MyException(e.what());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m.def("throws1", &throws1);
|
||||||
|
m.def("throws2", &throws2);
|
||||||
|
m.def("throws3", &throws3);
|
||||||
|
m.def("throws4", &throws4);
|
||||||
|
m.def("throws_logic_error", &throws_logic_error);
|
||||||
|
}
|
||||||
|
|
42
example/example19.py
Normal file
42
example/example19.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
sys.path.append('.')
|
||||||
|
|
||||||
|
import example
|
||||||
|
|
||||||
|
print("Can we catch a MyException?")
|
||||||
|
try:
|
||||||
|
example.throws1()
|
||||||
|
except example.MyException as e:
|
||||||
|
print(e.__class__.__name__, ":", e)
|
||||||
|
print("")
|
||||||
|
|
||||||
|
print("Can we translate to standard Python exceptions?")
|
||||||
|
try:
|
||||||
|
example.throws2()
|
||||||
|
except Exception as e:
|
||||||
|
print(e.__class__.__name__, ":", e)
|
||||||
|
print("")
|
||||||
|
|
||||||
|
print("Can we handle unknown exceptions?")
|
||||||
|
try:
|
||||||
|
example.throws3()
|
||||||
|
except Exception as e:
|
||||||
|
print(e.__class__.__name__, ":", e)
|
||||||
|
print("")
|
||||||
|
|
||||||
|
print("Can we delegate to another handler by rethrowing?")
|
||||||
|
try:
|
||||||
|
example.throws4()
|
||||||
|
except example.MyException as e:
|
||||||
|
print(e.__class__.__name__, ":", e)
|
||||||
|
print("")
|
||||||
|
|
||||||
|
print("Can we fall-through to the default handler?")
|
||||||
|
try:
|
||||||
|
example.throws_logic_error()
|
||||||
|
except Exception as e:
|
||||||
|
print(e.__class__.__name__, ":", e)
|
||||||
|
print("")
|
||||||
|
|
15
example/example19.ref
Normal file
15
example/example19.ref
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Can we catch a MyException?
|
||||||
|
MyException : this error should go to a custom type
|
||||||
|
|
||||||
|
Can we translate to standard Python exceptions?
|
||||||
|
RuntimeError : this error should go to a standard Python exception
|
||||||
|
|
||||||
|
Can we handle unknown exceptions?
|
||||||
|
RuntimeError : Caught an unknown exception!
|
||||||
|
|
||||||
|
Can we delegate to another handler by rethrowing?
|
||||||
|
MyException : this error is rethrown
|
||||||
|
|
||||||
|
Can we fall-through to the default handler?
|
||||||
|
RuntimeError : this error should fall through to the standard handler
|
||||||
|
|
@ -49,6 +49,27 @@ PYBIND11_NOINLINE inline internals &get_internals() {
|
|||||||
internals_ptr->istate = tstate->interp;
|
internals_ptr->istate = tstate->interp;
|
||||||
#endif
|
#endif
|
||||||
builtins[id] = capsule(internals_ptr);
|
builtins[id] = capsule(internals_ptr);
|
||||||
|
internals_ptr->registered_exception_translators.push_front(
|
||||||
|
[](std::exception_ptr p) -> void {
|
||||||
|
try {
|
||||||
|
if (p) std::rethrow_exception(p);
|
||||||
|
} catch (const error_already_set &) { return;
|
||||||
|
} catch (const index_error &e) { PyErr_SetString(PyExc_IndexError, e.what()); return;
|
||||||
|
} catch (const value_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
|
||||||
|
} catch (const stop_iteration &e) { PyErr_SetString(PyExc_StopIteration, e.what()); return;
|
||||||
|
} catch (const std::bad_alloc &e) { PyErr_SetString(PyExc_MemoryError, e.what()); return;
|
||||||
|
} catch (const std::domain_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
|
||||||
|
} catch (const std::invalid_argument &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
|
||||||
|
} catch (const std::length_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
|
||||||
|
} catch (const std::out_of_range &e) { PyErr_SetString(PyExc_IndexError, e.what()); return;
|
||||||
|
} catch (const std::range_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return;
|
||||||
|
} catch (const std::exception &e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return;
|
||||||
|
} catch (...) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Caught an unknown exception!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return *internals_ptr;
|
return *internals_ptr;
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
# pragma warning(pop)
|
# pragma warning(pop)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <forward_list>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@ -264,6 +265,7 @@ struct internals {
|
|||||||
std::unordered_map<const void *, void*> registered_types_py; // PyTypeObject* -> type_info
|
std::unordered_map<const void *, void*> registered_types_py; // PyTypeObject* -> type_info
|
||||||
std::unordered_map<const void *, void*> registered_instances; // void * -> PyObject*
|
std::unordered_map<const void *, void*> registered_instances; // void * -> PyObject*
|
||||||
std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache;
|
std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache;
|
||||||
|
std::forward_list<void (*) (std::exception_ptr)> registered_exception_translators;
|
||||||
#if defined(WITH_THREAD)
|
#if defined(WITH_THREAD)
|
||||||
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
|
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
|
||||||
PyInterpreterState *istate = nullptr;
|
PyInterpreterState *istate = nullptr;
|
||||||
|
@ -415,19 +415,32 @@ protected:
|
|||||||
if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD)
|
if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (const error_already_set &) { return nullptr;
|
} catch (const error_already_set &) {
|
||||||
} catch (const index_error &e) { PyErr_SetString(PyExc_IndexError, e.what()); return nullptr;
|
return nullptr;
|
||||||
} catch (const value_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return nullptr;
|
|
||||||
} catch (const stop_iteration &e) { PyErr_SetString(PyExc_StopIteration, e.what()); return nullptr;
|
|
||||||
} catch (const std::bad_alloc &e) { PyErr_SetString(PyExc_MemoryError, e.what()); return nullptr;
|
|
||||||
} catch (const std::domain_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return nullptr;
|
|
||||||
} catch (const std::invalid_argument &e) { PyErr_SetString(PyExc_ValueError, e.what()); return nullptr;
|
|
||||||
} catch (const std::length_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return nullptr;
|
|
||||||
} catch (const std::out_of_range &e) { PyErr_SetString(PyExc_IndexError, e.what()); return nullptr;
|
|
||||||
} catch (const std::range_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return nullptr;
|
|
||||||
} catch (const std::exception &e) { PyErr_SetString(PyExc_RuntimeError, e.what()); return nullptr;
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Caught an unknown exception!");
|
/* When an exception is caught, give each registered exception
|
||||||
|
translator a chance to translate it to a Python exception
|
||||||
|
in reverse order of registration.
|
||||||
|
|
||||||
|
A translator may choose to do one of the following:
|
||||||
|
|
||||||
|
- catch the exception and call PyErr_SetString or PyErr_SetObject
|
||||||
|
to set a standard (or custom) Python exception, or
|
||||||
|
- do nothing and let the exception fall through to the next translator, or
|
||||||
|
- delegate translation to the next translator by throwing a new type of exception. */
|
||||||
|
|
||||||
|
auto last_exception = std::current_exception();
|
||||||
|
auto ®istered_exception_translators = pybind11::detail::get_internals().registered_exception_translators;
|
||||||
|
for (auto& translator : registered_exception_translators) {
|
||||||
|
try {
|
||||||
|
translator(last_exception);
|
||||||
|
} catch (...) {
|
||||||
|
last_exception = std::current_exception();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
PyErr_SetString(PyExc_SystemError, "Exception escaped from default exception translator!");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1103,6 +1116,31 @@ template <typename InputType, typename OutputType> void implicitly_convertible()
|
|||||||
((detail::type_info *) it->second)->implicit_conversions.push_back(implicit_caster);
|
((detail::type_info *) it->second)->implicit_conversions.push_back(implicit_caster);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename ExceptionTranslator>
|
||||||
|
void register_exception_translator(ExceptionTranslator&& translator) {
|
||||||
|
detail::get_internals().registered_exception_translators.push_front(
|
||||||
|
std::forward<ExceptionTranslator>(translator));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wrapper to generate a new Python exception type.
|
||||||
|
*
|
||||||
|
* This should only be used with PyErr_SetString for now.
|
||||||
|
* It is not (yet) possible to use as a py::base.
|
||||||
|
* Template type argument is reserved for future use.
|
||||||
|
*/
|
||||||
|
template <typename type>
|
||||||
|
class exception : public object {
|
||||||
|
public:
|
||||||
|
exception(module &m, const std::string name, PyObject* base=PyExc_Exception) {
|
||||||
|
std::string full_name = std::string(PyModule_GetName(m.ptr()))
|
||||||
|
+ std::string(".") + name;
|
||||||
|
char* exception_name = const_cast<char*>(full_name.c_str());
|
||||||
|
m_ptr = PyErr_NewException(exception_name, base, NULL);
|
||||||
|
inc_ref(); // PyModule_AddObject() steals a reference
|
||||||
|
PyModule_AddObject(m.ptr(), name.c_str(), m_ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#if defined(WITH_THREAD)
|
#if defined(WITH_THREAD)
|
||||||
|
|
||||||
/* The functions below essentially reproduce the PyGILState_* API using a RAII
|
/* The functions below essentially reproduce the PyGILState_* API using a RAII
|
||||||
|
Loading…
Reference in New Issue
Block a user