Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add py::set_error(), use in updated py::exception<> documentation #4772

Merged
merged 14 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions docs/advanced/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ before a global translator is tried.
Inside the translator, ``std::rethrow_exception`` should be used within
a try block to re-throw the exception. One or more catch clauses to catch
the appropriate exceptions should then be used with each clause using
``PyErr_SetString`` to set a Python exception or ``ex(string)`` to set
the python exception to a custom exception type (see below).
``py::set_error()`` (see below).

To declare a custom Python exception type, declare a ``py::exception`` variable
and use this in the associated exception translator (note: it is often useful
Expand All @@ -142,14 +141,17 @@ standard python RuntimeError:

.. code-block:: cpp

static py::exception<MyCustomException> exc(m, "MyCustomError");
// This is a static object, so we must leak the Python reference:
// It is undefined when the destructor will run, possibly only after the
// Python interpreter is finalized already.
static py::handle exc = py::exception<MyCustomException>(m, "MyCustomError").release();
py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) std::rethrow_exception(p);
} catch (const MyCustomException &e) {
exc(e.what());
py::set_error(exc, e.what());
} catch (const OtherException &e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
py::set_error(PyExc_RuntimeError, e.what());
}
});

Expand All @@ -168,8 +170,7 @@ section.

.. note::

Call either ``PyErr_SetString`` or a custom exception's call
operator (``exc(string)``) for every exception caught in a custom exception
Call ``py::set_error()`` 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``.

Expand Down Expand Up @@ -200,7 +201,7 @@ If module1 has the following translator:
try {
if (p) std::rethrow_exception(p);
} catch (const std::invalid_argument &e) {
PyErr_SetString("module1 handled this")
py::set_error(PyExc_ArgumentError, "module1 handled this");
}
}

Expand All @@ -212,7 +213,7 @@ and module2 has the following similar translator:
try {
if (p) std::rethrow_exception(p);
} catch (const std::invalid_argument &e) {
PyErr_SetString("module2 handled this")
py::set_error(PyExc_ArgumentError, "module2 handled this");
}
}

Expand Down Expand Up @@ -312,11 +313,11 @@ error protocol, which is outlined here.
After calling the Python C API, if Python returns an error,
``throw py::error_already_set();``, which allows pybind11 to deal with the
exception and pass it back to the Python interpreter. This includes calls to
the error setting functions such as ``PyErr_SetString``.
the error setting functions such as ``py::set_error()``.

.. code-block:: cpp

PyErr_SetString(PyExc_TypeError, "C API type error demo");
py::set_error(PyExc_TypeError, "C API type error demo");
throw py::error_already_set();

// But it would be easier to simply...
Expand Down
6 changes: 3 additions & 3 deletions include/pybind11/detail/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *,
extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
PyTypeObject *type = Py_TYPE(self);
std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!";
PyErr_SetString(PyExc_TypeError, msg.c_str());
set_error(PyExc_TypeError, msg.c_str());
return -1;
}

Expand Down Expand Up @@ -579,15 +579,15 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
if (view) {
view->obj = nullptr;
}
PyErr_SetString(PyExc_BufferError, "pybind11_getbuffer(): Internal error");
set_error(PyExc_BufferError, "pybind11_getbuffer(): Internal error");
return -1;
}
std::memset(view, 0, sizeof(Py_buffer));
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
delete info;
// view->obj = nullptr; // Was just memset to 0, so not necessary
PyErr_SetString(PyExc_BufferError, "Writable buffer requested for readonly storage");
set_error(PyExc_BufferError, "Writable buffer requested for readonly storage");
return -1;
}
view->obj = obj;
Expand Down
2 changes: 1 addition & 1 deletion include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ PYBIND11_WARNING_POP
return nullptr; \
} \
catch (const std::exception &e) { \
PyErr_SetString(PyExc_ImportError, e.what()); \
::pybind11::set_error(PyExc_ImportError, e.what()); \
return nullptr; \
}

Expand Down
2 changes: 1 addition & 1 deletion include/pybind11/detail/internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ inline bool raise_err(PyObject *exc_type, const char *msg) {
raise_from(exc_type, msg);
return true;
}
PyErr_SetString(exc_type, msg);
set_error(exc_type, msg);
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion include/pybind11/detail/type_caster_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ class type_caster_generic {
std::string tname = rtti_type ? rtti_type->name() : cast_type.name();
detail::clean_type_id(tname);
std::string msg = "Unregistered type : " + tname;
PyErr_SetString(PyExc_TypeError, msg.c_str());
set_error(PyExc_TypeError, msg.c_str());
return {nullptr, nullptr};
}

Expand Down
4 changes: 2 additions & 2 deletions include/pybind11/numpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,7 @@ class array : public buffer {
/// Create array from any object -- always returns a new reference
static PyObject *raw_array(PyObject *ptr, int ExtraFlags = 0) {
if (ptr == nullptr) {
PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array from a nullptr");
set_error(PyExc_ValueError, "cannot create a pybind11::array from a nullptr");
return nullptr;
}
return detail::npy_api::get().PyArray_FromAny_(
Expand Down Expand Up @@ -1155,7 +1155,7 @@ class array_t : public array {
/// Create array from any object -- always returns a new reference
static PyObject *raw_array_t(PyObject *ptr) {
if (ptr == nullptr) {
PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr");
set_error(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr");
return nullptr;
}
return detail::npy_api::get().PyArray_FromAny_(ptr,
Expand Down
22 changes: 11 additions & 11 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -694,9 +694,8 @@ class cpp_function : public function {
if (overloads->is_constructor) {
if (!parent
|| !PyObject_TypeCheck(parent.ptr(), (PyTypeObject *) overloads->scope.ptr())) {
PyErr_SetString(
PyExc_TypeError,
"__init__(self, ...) called with invalid or missing `self` argument");
set_error(PyExc_TypeError,
"__init__(self, ...) called with invalid or missing `self` argument");
return nullptr;
}

Expand Down Expand Up @@ -1007,7 +1006,7 @@ class cpp_function : public function {

A translator may choose to do one of the following:

- catch the exception and call PyErr_SetString or PyErr_SetObject
- catch the exception and call py::set_error()
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.
Expand All @@ -1023,8 +1022,7 @@ class cpp_function : public function {
return nullptr;
}

PyErr_SetString(PyExc_SystemError,
"Exception escaped from default exception translator!");
set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
return nullptr;
}

Expand Down Expand Up @@ -1125,7 +1123,7 @@ class cpp_function : public function {
raise_from(PyExc_TypeError, msg.c_str());
return nullptr;
}
PyErr_SetString(PyExc_TypeError, msg.c_str());
set_error(PyExc_TypeError, msg.c_str());
return nullptr;
}
if (!result) {
Expand All @@ -1138,7 +1136,7 @@ class cpp_function : public function {
raise_from(PyExc_TypeError, msg.c_str());
return nullptr;
}
PyErr_SetString(PyExc_TypeError, msg.c_str());
set_error(PyExc_TypeError, msg.c_str());
return nullptr;
}
if (overloads->is_constructor && !self_value_and_holder.holder_constructed()) {
Expand Down Expand Up @@ -2528,7 +2526,7 @@ inline void register_local_exception_translator(ExceptionTranslator &&translator
/**
* Wrapper to generate a new Python exception type.
*
* This should only be used with PyErr_SetString for now.
* This should only be used with py::set_error() for now.
* It is not (yet) possible to use as a py::base.
* Template type argument is reserved for future use.
*/
Expand All @@ -2549,7 +2547,9 @@ class exception : public object {
}

// Sets the current python exception to this exception object with the given message
void operator()(const char *message) { PyErr_SetString(m_ptr, message); }
PYBIND11_DEPRECATED("Please use py::set_error() instead "
"(https://github.com/pybind/pybind11/pull/4772)")
void operator()(const char *message) const { set_error(*this, message); }
};

PYBIND11_NAMESPACE_BEGIN(detail)
Expand Down Expand Up @@ -2581,7 +2581,7 @@ register_exception_impl(handle scope, const char *name, handle base, bool isLoca
try {
std::rethrow_exception(p);
} catch (const CppException &e) {
detail::get_exception_object<CppException>()(e.what());
set_error(detail::get_exception_object<CppException>(), e.what());
}
});
return ex;
Expand Down
8 changes: 8 additions & 0 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,14 @@ class handle : public detail::object_api<handle> {
#endif
};

inline void set_error(const handle &type, const char *message) {
PyErr_SetString(type.ptr(), message);
}

inline void set_error(const handle &type, const handle &value) {
PyErr_SetObject(type.ptr(), value.ptr());
}

/** \rst
Holds a reference to a Python object (with reference counting)

Expand Down
4 changes: 2 additions & 2 deletions tests/cross_module_interleaved_error_already_set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ namespace {
namespace py = pybind11;

void interleaved_error_already_set() {
PyErr_SetString(PyExc_RuntimeError, "1st error.");
py::set_error(PyExc_RuntimeError, "1st error.");
try {
throw py::error_already_set();
} catch (const py::error_already_set &) {
// The 2nd error could be conditional in a real application.
PyErr_SetString(PyExc_RuntimeError, "2nd error.");
py::set_error(PyExc_RuntimeError, "2nd error.");
} // Here the 1st error is destroyed before the 2nd error is fetched.
// The error_already_set dtor triggers a pybind11::detail::get_internals()
// call via pybind11::gil_scoped_acquire.
Expand Down
8 changes: 4 additions & 4 deletions tests/pybind11_cross_module_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
// test_exceptions.py
py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException");
m.def("raise_runtime_error", []() {
PyErr_SetString(PyExc_RuntimeError, "My runtime error");
py::set_error(PyExc_RuntimeError, "My runtime error");
throw py::error_already_set();
});
m.def("raise_value_error", []() {
PyErr_SetString(PyExc_ValueError, "My value error");
py::set_error(PyExc_ValueError, "My value error");
throw py::error_already_set();
});
m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); });
Expand All @@ -49,7 +49,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
std::rethrow_exception(p);
}
} catch (const shared_exception &e) {
PyErr_SetString(PyExc_KeyError, e.what());
py::set_error(PyExc_KeyError, e.what());
}
});

Expand All @@ -60,7 +60,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
std::rethrow_exception(p);
}
} catch (const LocalException &e) {
PyErr_SetString(PyExc_KeyError, e.what());
py::set_error(PyExc_KeyError, e.what());
}
});

Expand Down
Loading
Loading