mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-04-24 18:10:02 +02:00
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:
parent
bb5205b2e4
commit
edb06b1612
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user