restructures StateManager

- Updates logic of loading the state
- set_service_attribut_value_by_path expects serialized object instead of the value now
- uses loads instead of __convert_value_if_needed now
This commit is contained in:
Mose Müller 2024-03-28 10:21:28 +01:00
parent bb5205b2e4
commit edb06b1612
2 changed files with 59 additions and 54 deletions

View File

@ -5,16 +5,15 @@ from collections.abc import Callable
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, cast
import pydase.units as u
from pydase.data_service.data_service_cache import DataServiceCache from pydase.data_service.data_service_cache import DataServiceCache
from pydase.utils.helpers import ( from pydase.utils.helpers import (
get_object_attr_from_path, get_object_attr_from_path,
is_property_attribute, is_property_attribute,
parse_list_attr_and_index, parse_list_attr_and_index,
) )
from pydase.utils.serialization.deserializer import loads
from pydase.utils.serialization.serializer import ( from pydase.utils.serialization.serializer import (
SerializedObject, SerializedObject,
dump,
generate_serialized_data_paths, generate_serialized_data_paths,
get_nested_dict_by_path, get_nested_dict_by_path,
serialized_dict_is_nested_object, serialized_dict_is_nested_object,
@ -154,15 +153,17 @@ class StateManager:
return return
for path in generate_serialized_data_paths(json_dict): for path in generate_serialized_data_paths(json_dict):
if self.__is_loadable_state_attribute(path):
nested_json_dict = get_nested_dict_by_path(json_dict, path) nested_json_dict = get_nested_dict_by_path(json_dict, path)
nested_class_dict = self._data_service_cache.get_value_dict_from_cache(path) nested_class_dict = self._data_service_cache.get_value_dict_from_cache(
path
)
value, value_type = nested_json_dict["value"], nested_json_dict["type"] value_type = 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:
if self.__is_loadable_state_attribute(path): self.set_service_attribute_value_by_path(path, nested_json_dict)
self.set_service_attribute_value_by_path(path, value)
else: else:
logger.info( logger.info(
"Attribute type of '%s' changed from '%s' to " "Attribute type of '%s' changed from '%s' to "
@ -183,7 +184,7 @@ class StateManager:
def set_service_attribute_value_by_path( def set_service_attribute_value_by_path(
self, self,
path: str, path: str,
value: Any, serialized_value: SerializedObject,
) -> None: ) -> None:
""" """
Sets the value of an attribute in the service managed by the `StateManager` Sets the value of an attribute in the service managed by the `StateManager`
@ -206,34 +207,30 @@ class StateManager:
logger.debug("Attribute '%s' is read-only. Ignoring new value...", path) logger.debug("Attribute '%s' is read-only. Ignoring new value...", path)
return return
converted_value = self.__convert_value_if_needed(value, current_value_dict) if "full_access_path" not in serialized_value:
# Backwards compatibility for JSON files not containing the
# full_access_path
logger.warning(
"The format of your JSON file is out-of-date. This might lead "
"to unexpected errors. Please consider updating it."
)
serialized_value["full_access_path"] = current_value_dict[
"full_access_path"
]
# only set value when it has changed # only set value when it has changed
if self.__attr_value_has_changed(converted_value, current_value_dict["value"]): if self.__attr_value_has_changed(serialized_value, current_value_dict):
self.__update_attribute_by_path(path, converted_value) self.__update_attribute_by_path(path, serialized_value)
else: else:
logger.debug("Value of attribute '%s' has not changed...", path) logger.debug("Value of attribute '%s' has not changed...", path)
def __attr_value_has_changed(self, value_object: Any, current_value: Any) -> bool: def __attr_value_has_changed(
"""Check if the serialized value of `value_object` differs from `current_value`. self, serialized_new_value: Any, serialized_current_value: Any
) -> bool:
The method serializes `value_object` to compare it, which is mainly return not (
necessary for handling Quantity objects. serialized_new_value["type"] == serialized_current_value["type"]
""" and serialized_new_value["value"] == serialized_current_value["value"]
return dump(value_object)["value"] != current_value
# TODO: can we remove this? We can replace this with the loads function, no?
def __convert_value_if_needed(
self, value: Any, current_value_dict: SerializedObject
) -> Any:
if current_value_dict["type"] == "Quantity":
return u.convert_to_quantity(
value, cast(dict[str, Any], current_value_dict["value"])["unit"]
) )
if current_value_dict["type"] == "float" and not isinstance(value, float):
return float(value)
return value
def __update_attribute_by_path( def __update_attribute_by_path(
self, path: str, serialized_value: SerializedObject self, path: str, serialized_value: SerializedObject
@ -255,12 +252,24 @@ class StateManager:
if attr_cache_type in ("ColouredEnum", "Enum"): if attr_cache_type in ("ColouredEnum", "Enum"):
enum_attr = get_object_attr_from_path(target_obj, attr_name) enum_attr = get_object_attr_from_path(target_obj, attr_name)
# take the value of the existing enum class # take the value of the existing enum class
# TODO: this might break when you set a value from a different enum if serialized_value["type"] in ("ColouredEnum", "Enum"):
if isinstance(value, enum.Enum): try:
value = value.name setattr(
setattr(target_obj, attr_name, enum_attr.__class__[value]) target_obj,
elif attr_cache_type == "list": attr_name,
list_obj = get_object_attr_from_path_list(target_obj, [attr_name]) enum_attr.__class__[serialized_value["value"]],
)
return
except KeyError:
# This error will arise when setting an enum from another enum class
# In this case, we resort to loading the enum and setting it
# directly
pass
value = loads(serialized_value)
if attr_cache_type == "list":
list_obj = get_object_attr_from_path(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)

View File

@ -9,7 +9,7 @@ from pydase.data_service.data_service_observer import DataServiceObserver
from pydase.data_service.state_manager import StateManager from pydase.data_service.state_manager import StateManager
from pydase.utils.helpers import get_object_attr_from_path from pydase.utils.helpers import get_object_attr_from_path
from pydase.utils.logging import SocketIOHandler from pydase.utils.logging import SocketIOHandler
from pydase.utils.serialization.deserializer import Deserializer, loads from pydase.utils.serialization.deserializer import Deserializer
from pydase.utils.serialization.serializer import SerializedObject, dump from pydase.utils.serialization.serializer import SerializedObject, dump
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -129,13 +129,9 @@ def setup_sio_events(sio: socketio.AsyncServer, state_manager: StateManager) ->
async def update_value(sid: str, data: UpdateDict) -> SerializedObject | None: # type: ignore async def update_value(sid: str, data: UpdateDict) -> SerializedObject | None: # type: ignore
path = data["access_path"] path = data["access_path"]
# this should probably happen within the following function call -> can also
# look at the current type of the attribute at "path"
new_value = loads(data["value"])
try: try:
state_manager.set_service_attribute_value_by_path( state_manager.set_service_attribute_value_by_path(
path=path, value=new_value path=path, serialized_value=data["value"]
) )
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)