Added Context Manager ObservableResultCollector

This commit is contained in:
2025-12-09 22:50:13 +01:00
parent d8b1ef2e2c
commit dbb2ecec33
5 changed files with 358 additions and 6 deletions

View File

@@ -1,7 +1,8 @@
import pytest
from myutils.observable import NotObservableError, make_observable, bind, collect_return_values, unbind, unbind_all, \
has_listeners, get_listener_count, add_event_listener, ObservableEvent, remove_event_listener
has_listeners, get_listener_count, add_event_listener, ObservableEvent, remove_event_listener, \
ObservableResultCollector
# Test fixtures
@@ -1045,3 +1046,244 @@ def test_i_can_receive_changes_in_after_property_change_event_for_all_attributes
data.value = "new value"
assert on_after_change_results == [("number", 1, 5, [5, "5_1"]), ("value", "initial", "new value", ["new value"])]
# ============================================================================
# ObservableResultCollector tests
# ============================================================================
def test_i_can_create_collector_with_list_of_observables():
"""
ObservableResultCollector should accept a list of observable objects.
"""
data1 = make_observable(Data())
data2 = make_observable(Data())
collector = ObservableResultCollector([data1, data2])
assert collector.observables == [data1, data2]
assert collector.attr_name == ""
assert collector.event == ObservableEvent.AFTER_PROPERTY_CHANGE
assert collector.results == []
def test_i_can_create_collector_with_single_observable():
"""
ObservableResultCollector should automatically convert a single object to a list.
"""
data = make_observable(Data())
collector = ObservableResultCollector(data)
assert collector.observables == [data]
assert isinstance(collector.observables, list)
def test_i_can_collect_results_from_single_observable():
"""
Collector should collect results from a callback bound to an observable.
"""
data = make_observable(Data())
def callback(old, new):
return f"Changed from {old} to {new}"
bind(data, "value", callback)
with ObservableResultCollector(data) as collector:
data.value = "world"
assert collector.results == ["Changed from initial to world"]
def test_i_can_collect_results_from_multiple_callbacks():
"""
Collector should collect results from multiple callbacks on the same attribute.
"""
data = make_observable(Data())
def callback1(old, new):
return f"callback1: {new}"
def callback2(old, new):
return f"callback2: {new}"
bind(data, "value", callback1)
bind(data, "value", callback2)
with ObservableResultCollector(data) as collector:
data.value = "test"
assert collector.results == ["callback1: test", "callback2: test"]
def test_i_can_collect_results_from_multiple_observables():
"""
Collector should collect results from multiple observable objects.
"""
data1 = make_observable(Data())
data2 = make_observable(Data())
def callback(old, new):
return f"Changed to {new}"
bind(data1, "value", callback)
bind(data2, "value", callback)
with ObservableResultCollector([data1, data2]) as collector:
data1.value = "first"
data2.value = "second"
assert collector.results == ["Changed to first", "Changed to second"]
def test_i_can_collect_results_for_specific_attribute():
"""
Collector should only collect results for a specific attribute when specified.
"""
data = make_observable(Data())
def value_callback(old, new):
return f"value: {new}"
def number_callback(old, new):
return f"number: {new}"
bind(data, "value", value_callback)
bind(data, "number", number_callback)
with ObservableResultCollector(data, attr_name="value") as collector:
data.value = "test"
data.number = 42
# Should only collect results from value changes
assert collector.results == ["value: test"]
def test_i_can_collect_results_for_all_attributes():
"""
Collector with attr_name="" should collect results from all attributes.
"""
data = make_observable(Data())
def value_callback(old, new):
return f"value: {new}"
def number_callback(old, new):
return f"number: {new}"
bind(data, "value", value_callback)
bind(data, "number", number_callback)
with ObservableResultCollector(data, attr_name="") as collector:
data.value = "test"
data.number = 42
# Should collect results from all attributes
assert collector.results == ["value: test", "number: 42"]
def test_i_can_verify_listeners_are_removed_after_exit():
"""
Listeners should be automatically removed when exiting the context manager.
"""
data = make_observable(Data())
def callback(old, new):
return f"Changed to {new}"
bind(data, "value", callback)
event_name = ObservableEvent.AFTER_PROPERTY_CHANGE.value
# Before entering context, no event listeners
assert event_name not in data._listeners
with ObservableResultCollector(data) as collector:
# Inside context, event listener should be added
assert event_name in data._listeners
assert "" in data._listeners[event_name]
data.value = "test"
# After exiting context, event listener should be removed
assert event_name not in data._listeners
assert collector.results == ["Changed to test"]
def test_i_can_verify_listeners_are_removed_on_exception():
"""
Listeners should be removed even when an exception occurs in the with block.
"""
data = make_observable(Data())
def callback(old, new):
return f"Changed to {new}"
bind(data, "value", callback)
event_name = ObservableEvent.AFTER_PROPERTY_CHANGE.value
try:
with ObservableResultCollector(data) as collector:
data.value = "test"
raise ValueError("Test exception")
except ValueError:
pass
# Listener should be removed despite exception
assert event_name not in data._listeners
def test_i_can_access_results_through_collector_object():
"""
The collector object returned by __enter__ should provide access to results.
"""
data = make_observable(Data())
def callback(old, new):
return new * 2
bind(data, "number", callback)
with ObservableResultCollector(data) as collector:
data.number = 5
# Results should be accessible during the with block
assert collector.results == [10]
data.number = 10
assert collector.results == [10, 20]
def test_i_can_collect_empty_results_when_no_callbacks():
"""
Collector should have empty results if no callbacks are bound.
"""
data = make_observable(Data())
with ObservableResultCollector(data) as collector:
data.value = "test"
# No callbacks were bound, so results should be empty
assert collector.results == []
def test_i_can_collect_none_values():
"""
Collector should collect None values returned by callbacks.
"""
data = make_observable(Data())
def callback1(old, new):
return None
def callback2(old, new):
return "not none"
bind(data, "value", callback1)
bind(data, "value", callback2)
with ObservableResultCollector(data) as collector:
data.value = "test"
assert collector.results == [None, "not none"]