This is a very simple change in support of the PyCLIF-pybind11 integration work:

Merely an extra `if` to pre-empt `TypeError: pybind11::init(): factory function returned nullptr`. This allows the factory function to set a custom Python error instead.

Manually written factory functions could `throw error_already_set()` directly, but in the context of PyCLIF that is very difficult (compared to this very simple PR), because the factory function can be completely unaware of pybind11.
This commit is contained in:
Ralf W. Grosse-Kunstleve 2023-09-12 16:46:00 -07:00
parent 8c7b8dd0ae
commit bb02ebc654
3 changed files with 20 additions and 0 deletions

View File

@ -38,6 +38,9 @@ PYBIND11_NAMESPACE_BEGIN(initimpl)
inline void no_nullptr(void *ptr) { inline void no_nullptr(void *ptr) {
if (!ptr) { if (!ptr) {
if (PyErr_Occurred()) {
throw error_already_set();
}
throw type_error("pybind11::init(): factory function returned nullptr"); throw type_error("pybind11::init(): factory function returned nullptr");
} }
} }

View File

@ -412,6 +412,16 @@ TEST_SUBMODULE(factory_constructors, m) {
"__init__", [](NoisyAlloc &a, int i, const std::string &) { new (&a) NoisyAlloc(i); }); "__init__", [](NoisyAlloc &a, int i, const std::string &) { new (&a) NoisyAlloc(i); });
}); });
struct FactoryErrorAlreadySet {};
py::class_<FactoryErrorAlreadySet>(m, "FactoryErrorAlreadySet")
.def(py::init([](bool set_error) -> FactoryErrorAlreadySet * {
if (!set_error) {
return new FactoryErrorAlreadySet();
}
py::set_error(PyExc_ValueError, "factory sets error and returns nullptr");
return nullptr;
}));
// static_assert testing (the following def's should all fail with appropriate compilation // static_assert testing (the following def's should all fail with appropriate compilation
// errors): // errors):
#if 0 #if 0

View File

@ -514,3 +514,10 @@ def test_invalid_self():
str(excinfo.value) str(excinfo.value)
== "__init__(self, ...) called with invalid or missing `self` argument" == "__init__(self, ...) called with invalid or missing `self` argument"
) )
def test_factory_error_already_set():
obj = m.FactoryErrorAlreadySet(False)
assert isinstance(obj, m.FactoryErrorAlreadySet)
with pytest.raises(ValueError, match="factory sets error and returns nullptr"):
m.FactoryErrorAlreadySet(True)