Skip to content

Commit 690a115

Browse files
rwgkpre-commit-ci[bot]ketozhanghenryiiivirtuald
authored
Add py::set_error(), use in updated py::exception<> documentation (#4772)
* Copy clang 17 compatibility fixes from PR #4762 to a separate PR. * static py::exception<> -> static py::handle * Add `py::set_error()` but also try the suggestion of @malfet (pytorch/pytorch#106401 (review)). * clang 17 compatibility fixes (#4767) * Copy clang 17 compatibility fixes from PR #4762 to a separate PR. * Add gcc:13 C++20 * Add silkeh/clang:16-bullseye C++20 * chore(deps): update pre-commit hooks (#4770) updates: - [github.com/psf/black: 23.3.0 → 23.7.0](psf/black@23.3.0...23.7.0) - [github.com/astral-sh/ruff-pre-commit: v0.0.276 → v0.0.281](astral-sh/ruff-pre-commit@v0.0.276...v0.0.281) - [github.com/asottile/blacken-docs: 1.14.0 → 1.15.0](adamchainz/blacken-docs@1.14.0...1.15.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * docs: Remove upper bound on pybind11 in example pyproject.toml for setuptools (#4774) * docs: Remove upper bound on pybind11 in example pyproject.toml for setuptools * Update docs/compiling.rst --------- Co-authored-by: Henry Schreiner <[email protected]> * Provide better type hints for a variety of generic types (#4259) * Provide better type hints for a variety of generic types * Makes better documentation * tuple, dict, list, set, function * Move to py::typing * style: pre-commit fixes * Update copyright line with correct year and actual author. The author information was copy-pasted from the git log output. --------- Co-authored-by: Ralf W. Grosse-Kunstleve <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Use `py::set_error()` everywhere possible (only one special case, in common.h). Overload `py::set_error(py::handle, py::handle)`. Change back to `static py::handle exc = ... .release();` Deprecate `py::exception<>::operator()` * Add `PYBIND11_WARNING_DISABLE` for INTEL and MSVC (and sort alphabetically). * `PYBIND11_WARNING_DISABLE_INTEL(10441)` does not work. For ICC only, falling back to the recommended `py::set_error()` to keep the testing simple. It is troublesome to add `--diag-disable=10441` specifically for test_exceptions.cpp, even that is non-ideal because it covers the entire file, not just the one line we need it for, and the value of exercising the trivial deprecated `operator()` on this one extra platform is practically zero. * Fix silly oversight. * NVHPC 23.5.0 generates deprecation warnings. They are currently not treated as errors, but falling back to using `py::set_error()` to not have to deal with that distraction. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Keto D. Zhang <[email protected]> Co-authored-by: Henry Schreiner <[email protected]> Co-authored-by: Dustin Spicuzza <[email protected]>
1 parent 824dc27 commit 690a115

15 files changed

+105
-54
lines changed

docs/advanced/exceptions.rst

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,7 @@ before a global translator is tried.
127127
Inside the translator, ``std::rethrow_exception`` should be used within
128128
a try block to re-throw the exception. One or more catch clauses to catch
129129
the appropriate exceptions should then be used with each clause using
130-
``PyErr_SetString`` to set a Python exception or ``ex(string)`` to set
131-
the python exception to a custom exception type (see below).
130+
``py::set_error()`` (see below).
132131

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

143142
.. code-block:: cpp
144143
145-
static py::exception<MyCustomException> exc(m, "MyCustomError");
144+
// This is a static object, so we must leak the Python reference:
145+
// It is undefined when the destructor will run, possibly only after the
146+
// Python interpreter is finalized already.
147+
static py::handle exc = py::exception<MyCustomException>(m, "MyCustomError").release();
146148
py::register_exception_translator([](std::exception_ptr p) {
147149
try {
148150
if (p) std::rethrow_exception(p);
149151
} catch (const MyCustomException &e) {
150-
exc(e.what());
152+
py::set_error(exc, e.what());
151153
} catch (const OtherException &e) {
152-
PyErr_SetString(PyExc_RuntimeError, e.what());
154+
py::set_error(PyExc_RuntimeError, e.what());
153155
}
154156
});
155157
@@ -168,8 +170,7 @@ section.
168170

169171
.. note::
170172

171-
Call either ``PyErr_SetString`` or a custom exception's call
172-
operator (``exc(string)``) for every exception caught in a custom exception
173+
Call ``py::set_error()`` for every exception caught in a custom exception
173174
translator. Failure to do so will cause Python to crash with ``SystemError:
174175
error return without exception set``.
175176

@@ -200,7 +201,7 @@ If module1 has the following translator:
200201
try {
201202
if (p) std::rethrow_exception(p);
202203
} catch (const std::invalid_argument &e) {
203-
PyErr_SetString("module1 handled this")
204+
py::set_error(PyExc_ArgumentError, "module1 handled this");
204205
}
205206
}
206207
@@ -212,7 +213,7 @@ and module2 has the following similar translator:
212213
try {
213214
if (p) std::rethrow_exception(p);
214215
} catch (const std::invalid_argument &e) {
215-
PyErr_SetString("module2 handled this")
216+
py::set_error(PyExc_ArgumentError, "module2 handled this");
216217
}
217218
}
218219
@@ -312,11 +313,11 @@ error protocol, which is outlined here.
312313
After calling the Python C API, if Python returns an error,
313314
``throw py::error_already_set();``, which allows pybind11 to deal with the
314315
exception and pass it back to the Python interpreter. This includes calls to
315-
the error setting functions such as ``PyErr_SetString``.
316+
the error setting functions such as ``py::set_error()``.
316317

317318
.. code-block:: cpp
318319
319-
PyErr_SetString(PyExc_TypeError, "C API type error demo");
320+
py::set_error(PyExc_TypeError, "C API type error demo");
320321
throw py::error_already_set();
321322
322323
// But it would be easier to simply...

include/pybind11/detail/class.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *,
375375
extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
376376
PyTypeObject *type = Py_TYPE(self);
377377
std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!";
378-
PyErr_SetString(PyExc_TypeError, msg.c_str());
378+
set_error(PyExc_TypeError, msg.c_str());
379379
return -1;
380380
}
381381

@@ -579,15 +579,15 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
579579
if (view) {
580580
view->obj = nullptr;
581581
}
582-
PyErr_SetString(PyExc_BufferError, "pybind11_getbuffer(): Internal error");
582+
set_error(PyExc_BufferError, "pybind11_getbuffer(): Internal error");
583583
return -1;
584584
}
585585
std::memset(view, 0, sizeof(Py_buffer));
586586
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
587587
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
588588
delete info;
589589
// view->obj = nullptr; // Was just memset to 0, so not necessary
590-
PyErr_SetString(PyExc_BufferError, "Writable buffer requested for readonly storage");
590+
set_error(PyExc_BufferError, "Writable buffer requested for readonly storage");
591591
return -1;
592592
}
593593
view->obj = obj;

include/pybind11/detail/common.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ PYBIND11_WARNING_POP
399399
return nullptr; \
400400
} \
401401
catch (const std::exception &e) { \
402-
PyErr_SetString(PyExc_ImportError, e.what()); \
402+
::pybind11::set_error(PyExc_ImportError, e.what()); \
403403
return nullptr; \
404404
}
405405

include/pybind11/detail/internals.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ inline bool raise_err(PyObject *exc_type, const char *msg) {
352352
raise_from(exc_type, msg);
353353
return true;
354354
}
355-
PyErr_SetString(exc_type, msg);
355+
set_error(exc_type, msg);
356356
return false;
357357
}
358358

include/pybind11/detail/type_caster_base.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ class type_caster_generic {
786786
std::string tname = rtti_type ? rtti_type->name() : cast_type.name();
787787
detail::clean_type_id(tname);
788788
std::string msg = "Unregistered type : " + tname;
789-
PyErr_SetString(PyExc_TypeError, msg.c_str());
789+
set_error(PyExc_TypeError, msg.c_str());
790790
return {nullptr, nullptr};
791791
}
792792

include/pybind11/numpy.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ class array : public buffer {
10081008
/// Create array from any object -- always returns a new reference
10091009
static PyObject *raw_array(PyObject *ptr, int ExtraFlags = 0) {
10101010
if (ptr == nullptr) {
1011-
PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array from a nullptr");
1011+
set_error(PyExc_ValueError, "cannot create a pybind11::array from a nullptr");
10121012
return nullptr;
10131013
}
10141014
return detail::npy_api::get().PyArray_FromAny_(
@@ -1155,7 +1155,7 @@ class array_t : public array {
11551155
/// Create array from any object -- always returns a new reference
11561156
static PyObject *raw_array_t(PyObject *ptr) {
11571157
if (ptr == nullptr) {
1158-
PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr");
1158+
set_error(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr");
11591159
return nullptr;
11601160
}
11611161
return detail::npy_api::get().PyArray_FromAny_(ptr,

include/pybind11/pybind11.h

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -694,9 +694,8 @@ class cpp_function : public function {
694694
if (overloads->is_constructor) {
695695
if (!parent
696696
|| !PyObject_TypeCheck(parent.ptr(), (PyTypeObject *) overloads->scope.ptr())) {
697-
PyErr_SetString(
698-
PyExc_TypeError,
699-
"__init__(self, ...) called with invalid or missing `self` argument");
697+
set_error(PyExc_TypeError,
698+
"__init__(self, ...) called with invalid or missing `self` argument");
700699
return nullptr;
701700
}
702701

@@ -1007,7 +1006,7 @@ class cpp_function : public function {
10071006
10081007
A translator may choose to do one of the following:
10091008
1010-
- catch the exception and call PyErr_SetString or PyErr_SetObject
1009+
- catch the exception and call py::set_error()
10111010
to set a standard (or custom) Python exception, or
10121011
- do nothing and let the exception fall through to the next translator, or
10131012
- delegate translation to the next translator by throwing a new type of exception.
@@ -1023,8 +1022,7 @@ class cpp_function : public function {
10231022
return nullptr;
10241023
}
10251024

1026-
PyErr_SetString(PyExc_SystemError,
1027-
"Exception escaped from default exception translator!");
1025+
set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
10281026
return nullptr;
10291027
}
10301028

@@ -1125,7 +1123,7 @@ class cpp_function : public function {
11251123
raise_from(PyExc_TypeError, msg.c_str());
11261124
return nullptr;
11271125
}
1128-
PyErr_SetString(PyExc_TypeError, msg.c_str());
1126+
set_error(PyExc_TypeError, msg.c_str());
11291127
return nullptr;
11301128
}
11311129
if (!result) {
@@ -1138,7 +1136,7 @@ class cpp_function : public function {
11381136
raise_from(PyExc_TypeError, msg.c_str());
11391137
return nullptr;
11401138
}
1141-
PyErr_SetString(PyExc_TypeError, msg.c_str());
1139+
set_error(PyExc_TypeError, msg.c_str());
11421140
return nullptr;
11431141
}
11441142
if (overloads->is_constructor && !self_value_and_holder.holder_constructed()) {
@@ -2528,7 +2526,7 @@ inline void register_local_exception_translator(ExceptionTranslator &&translator
25282526
/**
25292527
* Wrapper to generate a new Python exception type.
25302528
*
2531-
* This should only be used with PyErr_SetString for now.
2529+
* This should only be used with py::set_error() for now.
25322530
* It is not (yet) possible to use as a py::base.
25332531
* Template type argument is reserved for future use.
25342532
*/
@@ -2549,7 +2547,9 @@ class exception : public object {
25492547
}
25502548

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

25552555
PYBIND11_NAMESPACE_BEGIN(detail)
@@ -2581,7 +2581,7 @@ register_exception_impl(handle scope, const char *name, handle base, bool isLoca
25812581
try {
25822582
std::rethrow_exception(p);
25832583
} catch (const CppException &e) {
2584-
detail::get_exception_object<CppException>()(e.what());
2584+
set_error(detail::get_exception_object<CppException>(), e.what());
25852585
}
25862586
});
25872587
return ex;

include/pybind11/pytypes.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,14 @@ class handle : public detail::object_api<handle> {
334334
#endif
335335
};
336336

337+
inline void set_error(const handle &type, const char *message) {
338+
PyErr_SetString(type.ptr(), message);
339+
}
340+
341+
inline void set_error(const handle &type, const handle &value) {
342+
PyErr_SetObject(type.ptr(), value.ptr());
343+
}
344+
337345
/** \rst
338346
Holds a reference to a Python object (with reference counting)
339347

tests/cross_module_interleaved_error_already_set.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ namespace {
1616
namespace py = pybind11;
1717

1818
void interleaved_error_already_set() {
19-
PyErr_SetString(PyExc_RuntimeError, "1st error.");
19+
py::set_error(PyExc_RuntimeError, "1st error.");
2020
try {
2121
throw py::error_already_set();
2222
} catch (const py::error_already_set &) {
2323
// The 2nd error could be conditional in a real application.
24-
PyErr_SetString(PyExc_RuntimeError, "2nd error.");
24+
py::set_error(PyExc_RuntimeError, "2nd error.");
2525
} // Here the 1st error is destroyed before the 2nd error is fetched.
2626
// The error_already_set dtor triggers a pybind11::detail::get_internals()
2727
// call via pybind11::gil_scoped_acquire.

tests/pybind11_cross_module_tests.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
3131
// test_exceptions.py
3232
py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException");
3333
m.def("raise_runtime_error", []() {
34-
PyErr_SetString(PyExc_RuntimeError, "My runtime error");
34+
py::set_error(PyExc_RuntimeError, "My runtime error");
3535
throw py::error_already_set();
3636
});
3737
m.def("raise_value_error", []() {
38-
PyErr_SetString(PyExc_ValueError, "My value error");
38+
py::set_error(PyExc_ValueError, "My value error");
3939
throw py::error_already_set();
4040
});
4141
m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); });
@@ -49,7 +49,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
4949
std::rethrow_exception(p);
5050
}
5151
} catch (const shared_exception &e) {
52-
PyErr_SetString(PyExc_KeyError, e.what());
52+
py::set_error(PyExc_KeyError, e.what());
5353
}
5454
});
5555

@@ -60,7 +60,7 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
6060
std::rethrow_exception(p);
6161
}
6262
} catch (const LocalException &e) {
63-
PyErr_SetString(PyExc_KeyError, e.what());
63+
py::set_error(PyExc_KeyError, e.what());
6464
}
6565
});
6666

0 commit comments

Comments
 (0)