Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/pybind/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def value(self, new_value: _S) -> None:
raise NotImplementedError

@abstractmethod
def bind_to(self, value: Value[_S], already_bound_ok: bool = False) -> None:
def bind_to(self, value: Value[_S], already_bound_ok: bool = False, bind_weakly: bool = True) -> None:
raise NotImplementedError

@abstractmethod
Expand Down Expand Up @@ -117,7 +117,7 @@ def weak_observe(self, observer: Observer | ValueObserver[_S]) -> None:
def unobserve(self, observer: Observer | ValueObserver[_S]) -> None:
self._on_change.unobserve(observer)

def bind_to(self, value: Value[_S], already_bound_ok: bool = False) -> None:
def bind_to(self, value: Value[_S], already_bound_ok: bool = False, bind_weakly: bool = True) -> None:
if value is self:
raise RecursionError("Cannot bind a Variable to itself.")
if value.is_dependent_on(self):
Expand All @@ -128,7 +128,10 @@ def bind_to(self, value: Value[_S], already_bound_ok: bool = False) -> None:
if self._bound_to is value:
return
self.unbind()
value.observe(self._receive_bound_value)
if bind_weakly:
value.weak_observe(self._receive_bound_value)
else:
value.observe(self._receive_bound_value)
self._bound_to = value
self._bound_to_set = frozenset([value])
self._set_value_bypass_bound_check(value.value)
Expand Down
53 changes: 47 additions & 6 deletions tests/test_simple_variable.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import gc

import pytest
from pybind.values import SimpleVariable, Constant
from conftest import NoParametersObserver, OneParameterObserver
Expand Down Expand Up @@ -313,13 +315,52 @@ def test_simple_variable_deep_derived_from_diamond_pattern():
assert variable_top in dependencies


def test_constant_derived_from_empty():
constant = Constant("test")
def test_bind_weak_reference_clears():
root_var = SimpleVariable("root0")
dependent_var = SimpleVariable("")
dependent_var.bind_to(root_var, bind_weakly=True)

values = []
dependent_var.observe(lambda value: values.append(value))

root_var.value = "root1"
dependent_var = None
gc.collect()
root_var.value = "root2"

assert values == ["root1"]


def test_bind_strong_reference_stays():
root_var = SimpleVariable("root0")
dependent_var = SimpleVariable("")
dependent_var.bind_to(root_var, bind_weakly=False)

values = []
dependent_var.observe(lambda value: values.append(value))

root_var.value = "root1"
dependent_var = None
gc.collect()
root_var.value = "root2"

assert values == ["root1", "root2"]


def test_daisy_chain_variables_weak_reference_stays():
root_var = SimpleVariable("root0")
middle_var = SimpleVariable("")
dependent_var = SimpleVariable("")

assert constant.derived_from() == frozenset()
middle_var.bind_to(root_var, bind_weakly=True)
dependent_var.bind_to(middle_var, bind_weakly=False)

values = []
dependent_var.observe(lambda value: values.append(value))

def test_constant_deep_derived_from_empty():
constant = Constant("test")
root_var.value = "root1"
middle_var = None
gc.collect()
root_var.value = "root2"

assert list(constant.deep_derived_from) == []
assert values == ["root1", "root2"]