From 3440a632adb2a75e84ae643f1c439adf8b236cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Mon, 6 Nov 2023 18:26:38 +0100 Subject: [PATCH] moving set_nested_value_in_dict to Serializer, renaming module --- src/pydase/data_service/data_service.py | 2 +- src/pydase/data_service/data_service_cache.py | 4 +- src/pydase/utils/helpers.py | 83 ------------------- .../utils/{serialization.py => serializer.py} | 81 +++++++++++++++++- tests/utils/test_serialization.py | 2 +- 5 files changed, 83 insertions(+), 89 deletions(-) rename src/pydase/utils/{serialization.py => serializer.py} (69%) diff --git a/src/pydase/data_service/data_service.py b/src/pydase/data_service/data_service.py index 7147ed9..14097a3 100644 --- a/src/pydase/data_service/data_service.py +++ b/src/pydase/data_service/data_service.py @@ -20,7 +20,7 @@ from pydase.utils.helpers import ( parse_list_attr_and_index, update_value_if_changed, ) -from pydase.utils.serialization import Serializer +from pydase.utils.serializer import Serializer from pydase.utils.warnings import ( warn_if_instance_class_does_not_inherit_from_DataService, ) diff --git a/src/pydase/data_service/data_service_cache.py b/src/pydase/data_service/data_service_cache.py index 6a240ed..3693a77 100644 --- a/src/pydase/data_service/data_service_cache.py +++ b/src/pydase/data_service/data_service_cache.py @@ -1,7 +1,7 @@ import logging from typing import TYPE_CHECKING, Any -from pydase.utils.helpers import set_nested_value_in_dict +from pydase.utils.serializer import Serializer if TYPE_CHECKING: from pydase import DataService @@ -32,5 +32,5 @@ class DataServiceCache: # Construct the full path full_path = f"{parent_path}.{name}" if parent_path else name - set_nested_value_in_dict(self._cache, full_path, value) + Serializer.update_serialization_dict(self._cache, full_path, value) logger.debug(f"Cache updated at path: {full_path}, with value: {value}") diff --git a/src/pydase/utils/helpers.py b/src/pydase/utils/helpers.py index 14c327f..b30d2fc 100644 --- a/src/pydase/utils/helpers.py +++ b/src/pydase/utils/helpers.py @@ -272,89 +272,6 @@ def get_nested_value_from_DataService_by_path_and_key( return current_data.get(key, None) -def set_nested_value_in_dict(data_dict: dict[str, Any], path: str, value: Any) -> None: - """ - Set the value associated with a specific key in a dictionary given a path. - - This function traverses the dictionary according to the path provided and - sets the value at that path. The path is a string with dots connecting - the levels and brackets indicating list indices. - - Args: - cache (dict): The cache dictionary to set the value in. - path (str): The path to where the value should be set in the dictionary. - value (Any): The value to be set at the specified path in the dictionary. - - Examples: - Let's consider the following dictionary: - - cache = { - "attr1": {"type": "int", "value": 10}, - "attr2": { - "type": "MyClass", - "value": {"attr3": {"type": "float", "value": 20.5}} - } - } - - The function can be used to set the value of 'attr1' as follows: - set_nested_value_in_cache(cache, "attr1", 15) - - It can also be used to set the value of 'attr3', which is nested within 'attr2', - as follows: - set_nested_value_in_cache(cache, "attr2.attr3", 25.0) - """ - - parts = path.split(".") - current_dict: dict[str, Any] = data_dict - index: Optional[int] = None - - for attr_name in parts: - # Check if the key contains an index part like '[]' - if "[" in attr_name and attr_name.endswith("]"): - attr_name, index_part = attr_name.split("[", 1) - index_part = index_part.rstrip("]") # remove the closing bracket - - # Convert the index part to an integer - if index_part.isdigit(): - index = int(index_part) - else: - logger.error(f"Invalid index format in key: {attr_name}") - - current_dict = cast(dict[str, Any], current_dict.get(attr_name, None)) - - if not isinstance(current_dict, dict): - # key does not exist in dictionary, e.g. when class does not have this - # attribute - return - - if index is not None: - if 0 <= index < len(current_dict["value"]): - try: - current_dict = cast(dict[str, Any], current_dict["value"][index]) - except Exception as e: - logger.error(f"Could not change {path}. Exception: {e}") - return - else: - # TODO: appending to a list will probably be done here - logger.error(f"Could not change {path}...") - return - - # When the attribute is a class instance, the attributes are nested in the - # "value" key - if ( - current_dict["type"] not in STANDARD_TYPES - and current_dict["type"] != "method" - ): - current_dict = cast(dict[str, Any], current_dict.get("value", None)) # type: ignore - - index = None - - # setting the new value - try: - current_dict["value"] = value - except Exception as e: - logger.error(e) - def convert_arguments_to_hinted_types( args: dict[str, Any], type_hints: dict[str, Any] diff --git a/src/pydase/utils/serialization.py b/src/pydase/utils/serializer.py similarity index 69% rename from src/pydase/utils/serialization.py rename to src/pydase/utils/serializer.py index 674d530..af909aa 100644 --- a/src/pydase/utils/serialization.py +++ b/src/pydase/utils/serializer.py @@ -2,11 +2,15 @@ import inspect import logging from collections.abc import Callable from enum import Enum -from typing import Any, Optional +from typing import Any, Optional, cast import pydase.units as u from pydase.data_service.abstract_data_service import AbstractDataService -from pydase.utils.helpers import get_component_class_names +from pydase.utils.helpers import ( + STANDARD_TYPES, + get_component_class_names, + parse_list_attr_and_index, +) logger = logging.getLogger(__name__) @@ -212,6 +216,79 @@ class Serializer: "doc": doc, } + @staticmethod + def update_serialization_dict( + serialization_dict: dict[str, Any], path: str, value: Any + ) -> None: + """ + Set the value associated with a specific key in a dictionary given a path. + + This function traverses the dictionary according to the path provided and + sets the value at that path. The path is a string with dots connecting + the levels and brackets indicating list indices. + + Args: + data_dict (dict): The cache dictionary to set the value in. + path (str): The path to where the value should be set in the dictionary. + value (Any): The value to be set at the specified path in the dictionary. + + Examples: + Let's consider the following dictionary: + + cache = { + "attr1": {"type": "int", "value": 10}, + "attr2": { + "type": "MyClass", + "value": {"attr3": {"type": "float", "value": 20.5}} + } + } + + The function can be used to set the value of 'attr1' as follows: + set_nested_value_in_cache(cache, "attr1", 15) + + It can also be used to set the value of 'attr3', which is nested within + 'attr2', as follows: + set_nested_value_in_cache(cache, "attr2.attr3", 25.0) + """ + + parts, attr_name = path.split(".")[:-1], path.split(".")[-1] + current_dict: dict[str, Any] = serialization_dict + index: Optional[int] = None + + for path_part in parts: + # Check if the key contains an index part like 'attr_name[]' + path_part, index = parse_list_attr_and_index(path_part) + + current_dict = cast(dict[str, Any], current_dict.get(path_part, None)) + + if not isinstance(current_dict, dict): + # key does not exist in dictionary, e.g. when class does not have this + # attribute + return + + if index is not None: + try: + current_dict = cast(dict[str, Any], current_dict["value"][index]) + except Exception as e: + # TODO: appending to a list will probably be done here + logger.error(f"Could not change {path}... {e}") + return + + # When the attribute is a class instance, the attributes are nested in the + # "value" key + if ( + current_dict["type"] not in STANDARD_TYPES + and current_dict["type"] != "method" + ): + current_dict = cast(dict[str, Any], current_dict.get("value", None)) # type: ignore + + index = None + + # setting the new value + serialized_value = dump(value) + current_dict[attr_name]["value"] = serialized_value["value"] + current_dict[attr_name]["type"] = serialized_value["type"] + def dump(obj: Any) -> dict[str, Any]: return Serializer.serialize_object(obj) diff --git a/tests/utils/test_serialization.py b/tests/utils/test_serialization.py index 465128e..ce0311e 100644 --- a/tests/utils/test_serialization.py +++ b/tests/utils/test_serialization.py @@ -6,7 +6,7 @@ import pytest import pydase import pydase.units as u from pydase.components.coloured_enum import ColouredEnum -from pydase.utils.serialization import dump +from pydase.utils.serializer import dump @pytest.mark.parametrize(