diff --git a/pyproject.toml b/pyproject.toml index b3f99e6..7357bcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pydase" -version = "0.2.0" +version = "0.2.1" description = "A flexible and robust Python library for creating, managing, and interacting with data services, with built-in support for web and RPC servers, and customizable features for diverse use cases." authors = ["Mose Mueller "] readme = "README.md" diff --git a/src/pydase/data_service/callback_manager.py b/src/pydase/data_service/callback_manager.py index bfae070..94f6dc2 100644 --- a/src/pydase/data_service/callback_manager.py +++ b/src/pydase/data_service/callback_manager.py @@ -2,7 +2,7 @@ from __future__ import annotations import inspect from collections.abc import Callable -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast from loguru import logger @@ -55,9 +55,10 @@ class CallbackManager: self, obj: "AbstractDataService", parent_path: str ) -> None: """ - This method ensures that notifications are emitted whenever a list attribute of - a DataService instance changes. These notifications pertain solely to the list - item changes, not to changes in attributes of objects within the list. + This method ensures that notifications are emitted whenever a public list + attribute of a DataService instance changes. These notifications pertain solely + to the list item changes, not to changes in attributes of objects within the + list. The method works by converting all list attributes (both at the class and instance levels) into DataServiceList objects. Each DataServiceList is then @@ -101,6 +102,8 @@ class CallbackManager: value=value, ) if self.service == self.service.__root__ + # Skip private and protected lists + and not cast(str, attr_name).startswith("_") else None ) @@ -109,9 +112,12 @@ class CallbackManager: attr_value.add_callback(callback) continue if id(attr_value) in self._list_mapping: + # If the list `attr_value` was already referenced somewhere else notifying_list = self._list_mapping[id(attr_value)] notifying_list.add_callback(callback) else: + # convert the builtin list into a DataServiceList and add the + # callback notifying_list = DataServiceList(attr_value, callback=[callback]) self._list_mapping[id(attr_value)] = notifying_list diff --git a/tests/test_DataServiceList.py b/tests/test_DataServiceList.py index 5e9e1c7..a4b534b 100644 --- a/tests/test_DataServiceList.py +++ b/tests/test_DataServiceList.py @@ -99,3 +99,29 @@ def test_nested_reused_instance_list_attribute(capsys: CaptureFixture) -> None: ) actual_output = sorted(captured.out.strip().split("\n")) assert actual_output == expected_output + + +def test_protected_list_attribute(capsys: CaptureFixture) -> None: + """Changing protected lists should not emit notifications for the lists themselves, but + still for all properties depending on them. + """ + + class ServiceClass(DataService): + _attr = [0, 1] + + @property + def list_dependend_property(self) -> int: + return self._attr[0] + + service_instance = ServiceClass() + + service_instance._attr[0] = 1337 + captured = capsys.readouterr() + + expected_output = sorted( + [ + "ServiceClass.list_dependend_property = 1337", + ] + ) + actual_output = sorted(captured.out.strip().split("\n")) # type: ignore + assert actual_output == expected_output