From f5e6dca16a0ef6d63ad6bfea3d2ce704f44ebfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Thu, 9 Nov 2023 17:32:30 +0100 Subject: [PATCH 1/3] moves check for load_state decorator to load_state method in StateManager --- src/pydase/data_service/state_manager.py | 46 ++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/pydase/data_service/state_manager.py b/src/pydase/data_service/state_manager.py index 82b1c66..a1ad4d2 100644 --- a/src/pydase/data_service/state_manager.py +++ b/src/pydase/data_service/state_manager.py @@ -148,7 +148,10 @@ class StateManager: value, value_type = nested_json_dict["value"], nested_json_dict["type"] class_attr_value_type = nested_class_dict.get("type", None) - if class_attr_value_type == value_type: + if ( + class_attr_value_type == value_type + and self.__is_loadable_state_attribute(path) + ): self.set_service_attribute_value_by_path(path, value) else: logger.info( @@ -231,21 +234,36 @@ class StateManager: # Traverse the object according to the path parts target_obj = get_object_attr_from_path_list(self.service, parent_path_list) - if self.__attr_value_should_change(target_obj, attr_name): - if attr_cache_type in ("ColouredEnum", "Enum"): - enum_attr = get_object_attr_from_path_list(target_obj, [attr_name]) - setattr(target_obj, attr_name, enum_attr.__class__[value]) - elif attr_cache_type == "list": - list_obj = get_object_attr_from_path_list(target_obj, [attr_name]) - list_obj[index] = value - else: - setattr(target_obj, attr_name, value) + if attr_cache_type in ("ColouredEnum", "Enum"): + enum_attr = get_object_attr_from_path_list(target_obj, [attr_name]) + setattr(target_obj, attr_name, enum_attr.__class__[value]) + elif attr_cache_type == "list": + list_obj = get_object_attr_from_path_list(target_obj, [attr_name]) + list_obj[index] = value + else: + setattr(target_obj, attr_name, value) + + def __is_loadable_state_attribute(self, property_path: str) -> bool: + """Checks if an attribute defined by a dot-separated path should be loaded from + storage. + + For properties, it verifies the presence of the '@load_state' decorator. Regular + attributes default to being loadable. + """ + + parent_object = get_object_attr_from_path_list( + self.service, property_path.split(".")[:-1] + ) + attr_name = property_path.split(".")[-1] - def __attr_value_should_change(self, parent_object: Any, attr_name: str) -> bool: - # If the attribute is a property, change it using the setter without getting - # the property value (would otherwise be bad for expensive getter methods) prop = getattr(type(parent_object), attr_name, None) if isinstance(prop, property): - return has_load_state_decorator(prop) + has_decorator = has_load_state_decorator(prop) + if not has_decorator: + logger.debug( + f"Property {attr_name!r} has no '@load_state' decorator. " + "Ignoring value from JSON file..." + ) + return has_decorator return True From a9d577820f0e6649c4b743fa7eeb364253db6b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Thu, 9 Nov 2023 17:32:35 +0100 Subject: [PATCH 2/3] updates tests --- tests/data_service/test_state_manager.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/data_service/test_state_manager.py b/tests/data_service/test_state_manager.py index d7a0b7f..f572e47 100644 --- a/tests/data_service/test_state_manager.py +++ b/tests/data_service/test_state_manager.py @@ -153,7 +153,10 @@ def test_load_state(tmp_path: Path, caplog: LogCaptureFixture): assert service.subservice.name == "SubService" # didn't change assert "Service.some_unit changed to 12.0 A!" in caplog.text - assert "Attribute 'name' is read-only. Ignoring new value..." in caplog.text + assert ( + "Property 'name' has no '@load_state' decorator. " + "Ignoring value from JSON file..." in caplog.text + ) assert ( "Attribute type of 'some_float' changed from 'int' to 'float'. " "Ignoring value from JSON file..." @@ -195,7 +198,11 @@ def test_readonly_attribute(tmp_path: Path, caplog: LogCaptureFixture): service = Service() manager = StateManager(service=service, filename=str(file)) manager.load_state() - assert "Attribute 'name' is read-only. Ignoring new value..." in caplog.text + assert service.name == "Service" + assert ( + "Property 'name' has no '@load_state' decorator. " + "Ignoring value from JSON file..." in caplog.text + ) def test_changed_type(tmp_path: Path, caplog: LogCaptureFixture): From 2ab4d1c00aca0a14c94ee41bacdec70893aafb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Thu, 9 Nov 2023 17:33:03 +0100 Subject: [PATCH 3/3] updates to v0.3.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 98b7ebf..1fb86a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pydase" -version = "0.3.0" +version = "0.3.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"