mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 14:45:12 +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
|
||||
within pybind11.
|
||||
|
||||
.. _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
|
||||
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:
|
||||
|
||||
Treating STL data structures as opaque objects
|
||||
|
@ -25,6 +25,7 @@ set(PYBIND11_EXAMPLES
|
||||
example16.cpp
|
||||
example17.cpp
|
||||
example18.cpp
|
||||
example19.cpp
|
||||
issues.cpp
|
||||
)
|
||||
|
||||
|
@ -27,6 +27,7 @@ void init_ex15(py::module &);
|
||||
void init_ex16(py::module &);
|
||||
void init_ex17(py::module &);
|
||||
void init_ex18(py::module &);
|
||||
void init_ex19(py::module &);
|
||||
void init_issues(py::module &);
|
||||
|
||||
#if defined(PYBIND11_TEST_EIGEN)
|
||||
@ -54,6 +55,7 @@ PYBIND11_PLUGIN(example) {
|
||||
init_ex16(m);
|
||||
init_ex17(m);
|
||||
init_ex18(m);
|
||||
init_ex19(m);
|
||||
init_issues(m);
|
||||
|
||||
#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;
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
@ -66,6 +66,7 @@
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include <forward_list>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#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_instances; // void * -> PyObject*
|
||||
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)
|
||||
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
|
||||
PyInterpreterState *istate = nullptr;
|
||||
|
@ -415,19 +415,32 @@ protected:
|
||||
if (result.ptr() != PYBIND11_TRY_NEXT_OVERLOAD)
|
||||
break;
|
||||
}
|
||||
} catch (const error_already_set &) { return nullptr;
|
||||
} catch (const index_error &e) { PyErr_SetString(PyExc_IndexError, e.what()); 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 (const error_already_set &) {
|
||||
return nullptr;
|
||||
} 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;
|
||||
}
|
||||
|
||||
@ -1103,6 +1116,31 @@ template <typename InputType, typename OutputType> void implicitly_convertible()
|
||||
((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)
|
||||
|
||||
/* The functions below essentially reproduce the PyGILState_* API using a RAII
|
||||
|
Loading…
Reference in New Issue
Block a user