From bd7a46ddc1d8ba3a681d6d35c23c682d061d0828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Tue, 20 Feb 2024 12:26:43 +0100 Subject: [PATCH 1/5] changes are only registered if the containing object is not being changed as a whole --- src/pydase/data_service/data_service_observer.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pydase/data_service/data_service_observer.py b/src/pydase/data_service/data_service_observer.py index afcdc2b..aeabfef 100644 --- a/src/pydase/data_service/data_service_observer.py +++ b/src/pydase/data_service/data_service_observer.py @@ -23,6 +23,13 @@ class DataServiceObserver(PropertyObserver): super().__init__(state_manager.service) def on_change(self, full_access_path: str, value: Any) -> None: + if any( + full_access_path.startswith(changing_attribute) + and full_access_path != changing_attribute + for changing_attribute in self.changing_attributes + ): + return + cached_value_dict = deepcopy( self.state_manager._data_service_cache.get_value_dict_from_cache( full_access_path From c0ba23b0b25a4cf064bb52eaadeb6aed0aa9f499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Tue, 20 Feb 2024 12:28:34 +0100 Subject: [PATCH 2/5] appending to a list now also triggers _notify_change_start This helps in understanding if the list entries being added are "changing" themselves. Properties within the added objects will trigger property changes when they are serialized, so we have to tell the observer that he should not listen to them. --- src/pydase/observer_pattern/observable/observable_object.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pydase/observer_pattern/observable/observable_object.py b/src/pydase/observer_pattern/observable/observable_object.py index 1f56644..40ef632 100644 --- a/src/pydase/observer_pattern/observable/observable_object.py +++ b/src/pydase/observer_pattern/observable/observable_object.py @@ -148,6 +148,7 @@ class _ObservableList(ObservableObject, list[Any]): self._notify_changed(f"[{key}]", value) def append(self, __object: Any) -> None: + self._notify_change_start("") self._initialise_new_objects(f"[{len(self)}]", __object) super().append(__object) self._notify_changed("", self) From dc42bfaa9b12f958b2236e01513dec5b124e7cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Tue, 20 Feb 2024 12:29:30 +0100 Subject: [PATCH 3/5] removes changed_attribute path after on_change method --- src/pydase/observer_pattern/observer/observer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pydase/observer_pattern/observer/observer.py b/src/pydase/observer_pattern/observer/observer.py index b36dc6b..a73312c 100644 --- a/src/pydase/observer_pattern/observer/observer.py +++ b/src/pydase/observer_pattern/observer/observer.py @@ -14,11 +14,11 @@ class Observer(ABC): self.changing_attributes: list[str] = [] def _notify_changed(self, changed_attribute: str, value: Any) -> None: + self.on_change(full_access_path=changed_attribute, value=value) + if changed_attribute in self.changing_attributes: self.changing_attributes.remove(changed_attribute) - self.on_change(full_access_path=changed_attribute, value=value) - def _notify_change_start(self, changing_attribute: str) -> None: self.changing_attributes.append(changing_attribute) self.on_change_start(changing_attribute) From dfb6f966aabb43eec9e50582bb9abda00b868edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Tue, 20 Feb 2024 12:29:44 +0100 Subject: [PATCH 4/5] adds test for dynamic list entries with properties --- .../test_data_service_observer.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/data_service/test_data_service_observer.py b/tests/data_service/test_data_service_observer.py index 875d239..9e487fb 100644 --- a/tests/data_service/test_data_service_observer.py +++ b/tests/data_service/test_data_service_observer.py @@ -94,3 +94,31 @@ def test_protected_or_private_change_logs(caplog: pytest.LogCaptureFixture) -> N service.subclass._name = "Hello" assert "'subclass._name' changed to 'Hello'" not in caplog.text + + +def test_dynamic_list_entry_with_property(caplog: pytest.LogCaptureFixture) -> None: + class PropertyClass(pydase.DataService): + _name = "Hello" + + @property + def name(self) -> str: + """The name property.""" + return self._name + + class MyService(pydase.DataService): + def __init__(self) -> None: + super().__init__() + self.list_attr = [] + + def toggle_high_voltage(self) -> None: + self.list_attr = [] + self.list_attr.append(PropertyClass()) + self.list_attr[0]._name = "Hoooo" + + service = MyService() + state_manager = StateManager(service) + DataServiceObserver(state_manager) + service.toggle_high_voltage() + + assert "'list_attr[0].name' changed to 'Hello'" not in caplog.text + assert "'list_attr[0].name' changed to 'Hoooo'" in caplog.text From 72f6a8ddee66eac64d2d2f355b4483fab8726f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Tue, 20 Feb 2024 12:50:56 +0100 Subject: [PATCH 5/5] ignores some ruff rule --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 87fbe0b..14ae685 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ select = [ "W", # pycodestyle warnings ] ignore = [ + "RUF006", # asyncio-dangling-task "PERF203", # try-except-in-loop ]