Skip to content

Commit fa98804

Browse files
Adds set_name method of pybind11::capsule class (#3866)
* Adds set_name method of pybind11::capsule class This calls PyCapsule_SetName on the underlying capsule object. modified destructors to query capsules's Name [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Handle possible exception thrown by PyCapsule_GetName Also removed accidentally reintroduced use of `const char *&`. [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Fixed function name * Introduced private static function to reuse get_name_or_throw * added tests for capsule renaming * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * handle python error in flight * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Initialized PyObject * variables to nullptr * use write-unraisable if PyCapsule_GetName raises * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * get_name_or_throw->get_name_no_throw If PyCapsule_GetName raises an error we should write as unraisable to consume it and notify user, and then restore the error in flight if any. This way this method called from destructor would not modify interpreter error state. * used error_scope struct * Renamed get_name_no_throw->get_name_in_error_scope Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent ad0de0f commit fa98804

File tree

3 files changed

+77
-3
lines changed

3 files changed

+77
-3
lines changed

include/pybind11/pytypes.h

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,7 +1588,8 @@ class capsule : public object {
15881588
}
15891589
pybind11_fail("Unable to get capsule context");
15901590
}
1591-
void *ptr = PyCapsule_GetPointer(o, nullptr);
1591+
const char *name = get_name_in_error_scope(o);
1592+
void *ptr = PyCapsule_GetPointer(o, name);
15921593
if (ptr == nullptr) {
15931594
throw error_already_set();
15941595
}
@@ -1602,7 +1603,8 @@ class capsule : public object {
16021603

16031604
explicit capsule(void (*destructor)()) {
16041605
m_ptr = PyCapsule_New(reinterpret_cast<void *>(destructor), nullptr, [](PyObject *o) {
1605-
auto destructor = reinterpret_cast<void (*)()>(PyCapsule_GetPointer(o, nullptr));
1606+
const char *name = get_name_in_error_scope(o);
1607+
auto destructor = reinterpret_cast<void (*)()>(PyCapsule_GetPointer(o, name));
16061608
if (destructor == nullptr) {
16071609
throw error_already_set();
16081610
}
@@ -1637,7 +1639,33 @@ class capsule : public object {
16371639
}
16381640
}
16391641

1640-
const char *name() const { return PyCapsule_GetName(m_ptr); }
1642+
const char *name() const {
1643+
const char *name = PyCapsule_GetName(m_ptr);
1644+
if ((name == nullptr) && PyErr_Occurred()) {
1645+
throw error_already_set();
1646+
}
1647+
return name;
1648+
}
1649+
1650+
/// Replaces a capsule's name *without* calling the destructor on the existing one.
1651+
void set_name(const char *new_name) {
1652+
if (PyCapsule_SetName(m_ptr, new_name) != 0) {
1653+
throw error_already_set();
1654+
}
1655+
}
1656+
1657+
private:
1658+
static const char *get_name_in_error_scope(PyObject *o) {
1659+
error_scope error_guard;
1660+
1661+
const char *name = PyCapsule_GetName(o);
1662+
if ((name == nullptr) && PyErr_Occurred()) {
1663+
// write out and consume error raised by call to PyCapsule_GetName
1664+
PyErr_WriteUnraisable(o);
1665+
}
1666+
1667+
return name;
1668+
}
16411669
};
16421670

16431671
class tuple : public object {

tests/test_pytypes.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,33 @@ TEST_SUBMODULE(pytypes, m) {
159159
return py::capsule([]() { py::print("destructing capsule"); });
160160
});
161161

162+
m.def("return_renamed_capsule_with_destructor", []() {
163+
py::print("creating capsule");
164+
auto cap = py::capsule([]() { py::print("destructing capsule"); });
165+
static const char *capsule_name = "test_name1";
166+
py::print("renaming capsule");
167+
cap.set_name(capsule_name);
168+
return cap;
169+
});
170+
162171
m.def("return_capsule_with_destructor_2", []() {
163172
py::print("creating capsule");
164173
return py::capsule((void *) 1234, [](void *ptr) {
165174
py::print("destructing capsule: {}"_s.format((size_t) ptr));
166175
});
167176
});
168177

178+
m.def("return_renamed_capsule_with_destructor_2", []() {
179+
py::print("creating capsule");
180+
auto cap = py::capsule((void *) 1234, [](void *ptr) {
181+
py::print("destructing capsule: {}"_s.format((size_t) ptr));
182+
});
183+
static const char *capsule_name = "test_name2";
184+
py::print("renaming capsule");
185+
cap.set_name(capsule_name);
186+
return cap;
187+
});
188+
169189
m.def("return_capsule_with_name_and_destructor", []() {
170190
auto capsule = py::capsule((void *) 12345, "pointer type description", [](PyObject *ptr) {
171191
if (ptr) {

tests/test_pytypes.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,19 @@ def test_capsule(capture):
195195
"""
196196
)
197197

198+
with capture:
199+
a = m.return_renamed_capsule_with_destructor()
200+
del a
201+
pytest.gc_collect()
202+
assert (
203+
capture.unordered
204+
== """
205+
creating capsule
206+
renaming capsule
207+
destructing capsule
208+
"""
209+
)
210+
198211
with capture:
199212
a = m.return_capsule_with_destructor_2()
200213
del a
@@ -207,6 +220,19 @@ def test_capsule(capture):
207220
"""
208221
)
209222

223+
with capture:
224+
a = m.return_renamed_capsule_with_destructor_2()
225+
del a
226+
pytest.gc_collect()
227+
assert (
228+
capture.unordered
229+
== """
230+
creating capsule
231+
renaming capsule
232+
destructing capsule: 1234
233+
"""
234+
)
235+
210236
with capture:
211237
a = m.return_capsule_with_name_and_destructor()
212238
del a

0 commit comments

Comments
 (0)