mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-20 14:59:27 +00:00
type_caster_generic: add set_foreign_holder method for subclasses to implement (#5862)
* type_caster_generic: add set_foreign_holder method for subclasses to implement * style: pre-commit fixes * Rename try_shared_from_this -> set_via_shared_from_this to avoid confusion against try_get_shared_from_this * Add comment explaining the limits of the test * CI * style: pre-commit fixes * Fixes from code review * style: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -83,6 +83,7 @@ detail_headers = {
|
||||
"include/pybind11/detail/descr.h",
|
||||
"include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h",
|
||||
"include/pybind11/detail/function_record_pyobject.h",
|
||||
"include/pybind11/detail/holder_caster_foreign_helpers.h",
|
||||
"include/pybind11/detail/init.h",
|
||||
"include/pybind11/detail/internals.h",
|
||||
"include/pybind11/detail/native_enum_data.h",
|
||||
|
||||
@@ -25,8 +25,9 @@ using MixedLocalGlobal = LocalBase<4>;
|
||||
using MixedGlobalLocal = LocalBase<5>;
|
||||
|
||||
/// Registered with py::module_local only in the secondary module:
|
||||
using ExternalType1 = LocalBase<6>;
|
||||
using ExternalType2 = LocalBase<7>;
|
||||
using ExternalType1 = LocalBase<6>; // default holder
|
||||
using ExternalType2 = LocalBase<7>; // held by std::shared_ptr
|
||||
using ExternalType3 = LocalBase<8>; // held by smart_holder
|
||||
|
||||
using LocalVec = std::vector<LocalType>;
|
||||
using LocalVec2 = std::vector<NonLocal2>;
|
||||
@@ -65,11 +66,11 @@ PYBIND11_MAKE_OPAQUE(NonLocalMap)
|
||||
PYBIND11_MAKE_OPAQUE(NonLocalMap2)
|
||||
|
||||
// Simple bindings (used with the above):
|
||||
template <typename T, int Adjust = 0, typename... Args>
|
||||
py::class_<T> bind_local(Args &&...args) {
|
||||
return py::class_<T>(std::forward<Args>(args)...).def(py::init<int>()).def("get", [](T &i) {
|
||||
return i.i + Adjust;
|
||||
});
|
||||
template <typename T, int Adjust = 0, typename Holder = std::unique_ptr<T>, typename... Args>
|
||||
py::class_<T, Holder> bind_local(Args &&...args) {
|
||||
return py::class_<T, Holder>(std::forward<Args>(args)...)
|
||||
.def(py::init<int>())
|
||||
.def("get", [](T &i) { return i.i + Adjust; });
|
||||
}
|
||||
|
||||
// Simulate a foreign library base class (to match the example in the docs):
|
||||
|
||||
@@ -26,7 +26,9 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m, py::mod_gil_not_used()) {
|
||||
|
||||
// test_load_external
|
||||
bind_local<ExternalType1>(m, "ExternalType1", py::module_local());
|
||||
bind_local<ExternalType2>(m, "ExternalType2", py::module_local());
|
||||
bind_local<ExternalType2, 0, std::shared_ptr<ExternalType2>>(
|
||||
m, "ExternalType2", py::module_local());
|
||||
bind_local<ExternalType3, 0, py::smart_holder>(m, "ExternalType3", py::module_local());
|
||||
|
||||
// test_exceptions.py
|
||||
py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException");
|
||||
|
||||
@@ -21,6 +21,31 @@ TEST_SUBMODULE(local_bindings, m) {
|
||||
// test_load_external
|
||||
m.def("load_external1", [](ExternalType1 &e) { return e.i; });
|
||||
m.def("load_external2", [](ExternalType2 &e) { return e.i; });
|
||||
m.def("load_external3", [](ExternalType3 &e) { return e.i; });
|
||||
|
||||
struct SharedKeepAlive {
|
||||
std::shared_ptr<int> contents;
|
||||
int value() const { return contents ? *contents : -20251012; }
|
||||
long use_count() const { return contents.use_count(); }
|
||||
};
|
||||
py::class_<SharedKeepAlive>(m, "SharedKeepAlive")
|
||||
.def_property_readonly("value", &SharedKeepAlive::value)
|
||||
.def_property_readonly("use_count", &SharedKeepAlive::use_count);
|
||||
m.def("load_external2_shared", [](const std::shared_ptr<ExternalType2> &p) {
|
||||
return SharedKeepAlive{std::shared_ptr<int>(p, &p->i)};
|
||||
});
|
||||
m.def("load_external3_shared", [](const std::shared_ptr<ExternalType3> &p) {
|
||||
return SharedKeepAlive{std::shared_ptr<int>(p, &p->i)};
|
||||
});
|
||||
m.def("load_external1_unique", [](std::unique_ptr<ExternalType1> p) { return p->i; });
|
||||
m.def("load_external3_unique", [](std::unique_ptr<ExternalType3> p) { return p->i; });
|
||||
|
||||
// Aspects of set_foreign_holder that are not covered:
|
||||
// - loading a foreign instance into a custom holder should fail
|
||||
// - we're only covering the case where the local module doesn't know
|
||||
// about the type; the paths where it does (e.g., if both global and
|
||||
// foreign-module-local bindings exist for the same type) should work
|
||||
// the same way (they use the same code so they very likely do)
|
||||
|
||||
// test_local_bindings
|
||||
// Register a class with py::module_local:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import local_bindings as m
|
||||
@@ -11,6 +14,7 @@ def test_load_external():
|
||||
|
||||
assert m.load_external1(cm.ExternalType1(11)) == 11
|
||||
assert m.load_external2(cm.ExternalType2(22)) == 22
|
||||
assert m.load_external3(cm.ExternalType3(33)) == 33
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
assert m.load_external2(cm.ExternalType1(21)) == 21
|
||||
@@ -20,6 +24,36 @@ def test_load_external():
|
||||
assert m.load_external1(cm.ExternalType2(12)) == 12
|
||||
assert "incompatible function arguments" in str(excinfo.value)
|
||||
|
||||
def test_shared(val, ctor, loader):
|
||||
obj = ctor(val)
|
||||
with suppress(AttributeError): # non-cpython VMs don't have getrefcount
|
||||
rc_before = sys.getrefcount(obj)
|
||||
wrapper = loader(obj)
|
||||
# wrapper holds a shared_ptr that keeps obj alive
|
||||
assert wrapper.use_count == 1
|
||||
assert wrapper.value == val
|
||||
with suppress(AttributeError):
|
||||
rc_after = sys.getrefcount(obj)
|
||||
assert rc_after > rc_before
|
||||
|
||||
test_shared(220, cm.ExternalType2, m.load_external2_shared)
|
||||
test_shared(330, cm.ExternalType3, m.load_external3_shared)
|
||||
|
||||
with pytest.raises(TypeError, match="incompatible function arguments"):
|
||||
test_shared(320, cm.ExternalType2, m.load_external3_shared)
|
||||
with pytest.raises(TypeError, match="incompatible function arguments"):
|
||||
test_shared(230, cm.ExternalType3, m.load_external2_shared)
|
||||
|
||||
with pytest.raises(
|
||||
RuntimeError, match="Foreign instance cannot be converted to std::unique_ptr"
|
||||
):
|
||||
m.load_external1_unique(cm.ExternalType1(2200))
|
||||
|
||||
with pytest.raises(
|
||||
RuntimeError, match="Foreign instance cannot be converted to std::unique_ptr"
|
||||
):
|
||||
m.load_external3_unique(cm.ExternalType3(3300))
|
||||
|
||||
|
||||
def test_local_bindings():
|
||||
"""Tests that duplicate `py::module_local` class bindings work across modules"""
|
||||
|
||||
Reference in New Issue
Block a user