mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-04-21 00:40:01 +02:00
Merge pull request #68 from tiqi-group/fix/only_load_state_properties_can_be_updated
fix: only load state properties can be updated
This commit is contained in:
commit
a76035f443
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pydase"
|
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."
|
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 <mosmuell@ethz.ch>"]
|
authors = ["Mose Mueller <mosmuell@ethz.ch>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -148,7 +148,10 @@ class StateManager:
|
|||||||
value, value_type = nested_json_dict["value"], nested_json_dict["type"]
|
value, value_type = nested_json_dict["value"], nested_json_dict["type"]
|
||||||
class_attr_value_type = nested_class_dict.get("type", None)
|
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)
|
self.set_service_attribute_value_by_path(path, value)
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -231,21 +234,36 @@ class StateManager:
|
|||||||
# Traverse the object according to the path parts
|
# Traverse the object according to the path parts
|
||||||
target_obj = get_object_attr_from_path_list(self.service, parent_path_list)
|
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"):
|
||||||
if attr_cache_type in ("ColouredEnum", "Enum"):
|
enum_attr = get_object_attr_from_path_list(target_obj, [attr_name])
|
||||||
enum_attr = get_object_attr_from_path_list(target_obj, [attr_name])
|
setattr(target_obj, attr_name, enum_attr.__class__[value])
|
||||||
setattr(target_obj, attr_name, enum_attr.__class__[value])
|
elif attr_cache_type == "list":
|
||||||
elif attr_cache_type == "list":
|
list_obj = get_object_attr_from_path_list(target_obj, [attr_name])
|
||||||
list_obj = get_object_attr_from_path_list(target_obj, [attr_name])
|
list_obj[index] = value
|
||||||
list_obj[index] = value
|
else:
|
||||||
else:
|
setattr(target_obj, attr_name, value)
|
||||||
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)
|
prop = getattr(type(parent_object), attr_name, None)
|
||||||
|
|
||||||
if isinstance(prop, property):
|
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
|
return True
|
||||||
|
@ -153,7 +153,10 @@ def test_load_state(tmp_path: Path, caplog: LogCaptureFixture):
|
|||||||
assert service.subservice.name == "SubService" # didn't change
|
assert service.subservice.name == "SubService" # didn't change
|
||||||
|
|
||||||
assert "Service.some_unit changed to 12.0 A!" in caplog.text
|
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 (
|
assert (
|
||||||
"Attribute type of 'some_float' changed from 'int' to 'float'. "
|
"Attribute type of 'some_float' changed from 'int' to 'float'. "
|
||||||
"Ignoring value from JSON file..."
|
"Ignoring value from JSON file..."
|
||||||
@ -195,7 +198,11 @@ def test_readonly_attribute(tmp_path: Path, caplog: LogCaptureFixture):
|
|||||||
service = Service()
|
service = Service()
|
||||||
manager = StateManager(service=service, filename=str(file))
|
manager = StateManager(service=service, filename=str(file))
|
||||||
manager.load_state()
|
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):
|
def test_changed_type(tmp_path: Path, caplog: LogCaptureFixture):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user