Skip to content

Commit f436f65

Browse files
committed
weakrefs work again
1 parent 6b87a2e commit f436f65

6 files changed

Lines changed: 65 additions & 5 deletions

File tree

src/duckdb_py/pyconnection.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,9 @@ DuckDBPyConnection::RegisterScalarUDF(const string &name, const py::callable &ud
477477
}
478478

479479
void DuckDBPyConnection::Initialize(py::handle &m) {
480-
auto connection_module = py::class_<DuckDBPyConnection>(m, "DuckDBPyConnection");
480+
// Weak-referenceable like pybind11 (which set tp_weaklistoffset by default); nanobind requires the opt-in,
481+
// otherwise weakref.ref/proxy/finalize on a connection raises TypeError.
482+
auto connection_module = py::class_<DuckDBPyConnection>(m, "DuckDBPyConnection", py::is_weak_referenceable());
481483

482484
connection_module.def("__enter__", &DuckDBPyConnection::Enter)
483485
.def(

src/duckdb_py/pyexpression/initialize.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,8 @@ static void InitializeImplicitConversion(py::class_<DuckDBPyExpression> &m) {
325325
}
326326

327327
void DuckDBPyExpression::Initialize(py::module_ &m) {
328-
auto expression = py::class_<DuckDBPyExpression>(m, "Expression");
328+
// Weak-referenceable like pybind11 (nanobind requires the explicit opt-in).
329+
auto expression = py::class_<DuckDBPyExpression>(m, "Expression", py::is_weak_referenceable());
329330

330331
InitializeStaticMethods(m);
331332
InitializeDunderMethods(expression);

src/duckdb_py/pyrelation/initialize.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ static void InitializeMetaQueries(py::class_<DuckDBPyRelation> &m) {
278278
}
279279

280280
void DuckDBPyRelation::Initialize(py::handle &m) {
281-
auto relation_module = py::class_<DuckDBPyRelation>(m, "DuckDBPyRelation");
281+
// Weak-referenceable like pybind11 (nanobind requires the explicit opt-in).
282+
auto relation_module = py::class_<DuckDBPyRelation>(m, "DuckDBPyRelation", py::is_weak_referenceable());
282283
InitializeReadOnlyProperties(relation_module);
283284
InitializeAggregates(relation_module);
284285
InitializeWindowOperators(relation_module);

src/duckdb_py/pystatement.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ static void InitializeReadOnlyProperties(py::class_<DuckDBPyStatement> &m) {
1515
}
1616

1717
void DuckDBPyStatement::Initialize(py::handle &m) {
18-
auto relation_module = py::class_<DuckDBPyStatement>(m, "Statement");
18+
// Weak-referenceable like pybind11 (nanobind requires the explicit opt-in).
19+
auto relation_module = py::class_<DuckDBPyStatement>(m, "Statement", py::is_weak_referenceable());
1920
InitializeReadOnlyProperties(relation_module);
2021
}
2122

src/duckdb_py/typing/pytype.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,8 @@ bool DuckDBPyType::TryConvert(const py::object &object, std::unique_ptr<DuckDBPy
352352
}
353353

354354
void DuckDBPyType::Initialize(py::handle &m) {
355-
auto type_module = py::class_<DuckDBPyType>(m, "DuckDBPyType");
355+
// Weak-referenceable like pybind11 (nanobind requires the explicit opt-in).
356+
auto type_module = py::class_<DuckDBPyType>(m, "DuckDBPyType", py::is_weak_referenceable());
356357

357358
type_module.def("__repr__", &DuckDBPyType::ToString, "Stringified representation of the type object");
358359
type_module.def("__eq__", &DuckDBPyType::Equals, "Compare two types for equality", py::arg("other"),

tests/fast/test_weakref.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""Bound types must be weak-referenceable.
2+
3+
pybind11 set ``tp_weaklistoffset`` on every bound type by default, so
4+
``weakref.ref``/``proxy``/``finalize`` and ``WeakValueDictionary`` worked out of the box.
5+
nanobind opts out by default and requires ``py::is_weak_referenceable()`` at registration; without
6+
it those calls raise ``TypeError: cannot create weak reference``. This guards that regression for
7+
every publicly handed-out bound type (Connection, Relation, Expression, Type, Statement).
8+
"""
9+
10+
import platform
11+
import weakref
12+
13+
import pytest
14+
15+
import duckdb
16+
17+
pytestmark = pytest.mark.skipif(
18+
platform.system() == "Emscripten",
19+
reason="Extensions are not supported on Emscripten",
20+
)
21+
22+
23+
@pytest.fixture
24+
def bound_objects():
25+
con = duckdb.connect()
26+
objs = {
27+
"Connection": con,
28+
"Relation": con.sql("SELECT 42 AS a"),
29+
"Expression": duckdb.ColumnExpression("a"),
30+
"Type": duckdb.type("INTEGER"),
31+
"Statement": con.extract_statements("SELECT 42")[0],
32+
}
33+
yield objs
34+
con.close()
35+
36+
37+
@pytest.mark.parametrize(
38+
"name",
39+
["Connection", "Relation", "Expression", "Type", "Statement"],
40+
)
41+
def test_bound_type_is_weak_referenceable(bound_objects, name):
42+
obj = bound_objects[name]
43+
44+
ref = weakref.ref(obj)
45+
assert ref() is obj
46+
47+
weakref.proxy(obj) # must not raise
48+
49+
finalized = []
50+
weakref.finalize(obj, finalized.append, name)
51+
52+
wvd = weakref.WeakValueDictionary()
53+
wvd["k"] = obj
54+
assert wvd["k"] is obj

0 commit comments

Comments
 (0)