Merge pull request #197 from tiqi-group/fix/dict_key_normalization

Fix: dict key normalization
This commit is contained in:
Mose Müller 2024-12-20 14:43:04 +01:00 committed by GitHub
commit d6bad37233
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 28 deletions

View File

@ -10,7 +10,6 @@ from pydase.observer_pattern.observer.property_observer import (
)
from pydase.utils.helpers import (
get_object_attr_from_path,
normalize_full_access_path_string,
)
from pydase.utils.serialization.serializer import (
SerializationPathError,
@ -102,8 +101,7 @@ class DataServiceObserver(PropertyObserver):
)
def _notify_dependent_property_changes(self, changed_attr_path: str) -> None:
normalized_attr_path = normalize_full_access_path_string(changed_attr_path)
changed_props = self.property_deps_dict.get(normalized_attr_path, [])
changed_props = self.property_deps_dict.get(changed_attr_path, [])
for prop in changed_props:
# only notify about changing attribute if it is not currently being
# "changed" e.g. when calling the getter of a property within another

View File

@ -100,7 +100,7 @@ class PropertyObserver(Observer):
elif isinstance(collection, dict):
for key, val in collection.items():
if isinstance(val, Observable):
new_prefix = f"{parent_path}['{key}']"
new_prefix = f'{parent_path}["{key}"]'
deps.update(
self._get_properties_and_their_dependencies(val, new_prefix)
)

View File

@ -223,25 +223,3 @@ def current_event_loop_exists() -> bool:
import asyncio
return asyncio.get_event_loop_policy()._local._loop is not None # type: ignore
def normalize_full_access_path_string(s: str) -> str:
"""Normalizes a string representing a full access path by converting double quotes
to single quotes.
This function is useful for ensuring consistency in strings that represent access
paths containing dictionary keys, by replacing all double quotes (`"`) with single
quotes (`'`).
Args:
s (str): The input string to be normalized.
Returns:
A new string with all double quotes replaced by single quotes.
Example:
>>> normalize_full_access_path_string('dictionary["first"].my_task')
"dictionary['first'].my_task"
"""
return s.replace('"', "'")

View File

@ -167,8 +167,8 @@ def test_normalized_attr_path_in_dependent_property_changes(
state_manager = StateManager(service=service_instance)
observer = DataServiceObserver(state_manager=state_manager)
assert observer.property_deps_dict["service_dict['one']._prop"] == [
"service_dict['one'].prop"
assert observer.property_deps_dict['service_dict["one"]._prop'] == [
'service_dict["one"].prop'
]
# We can use dict key path encoded with double quotes
@ -184,3 +184,41 @@ def test_normalized_attr_path_in_dependent_property_changes(
)
assert service_instance.service_dict["one"].prop == 12.0
assert "'service_dict[\"one\"].prop' changed to '12.0'" in caplog.text
def test_nested_dict_property_changes(
caplog: pytest.LogCaptureFixture,
) -> None:
def get_voltage() -> float:
"""Mocking a remote device."""
return 2.0
def set_voltage(value: float) -> None:
"""Mocking a remote device."""
class OtherService(pydase.DataService):
_voltage = 1.0
@property
def voltage(self) -> float:
# Property dependency _voltage changes within the property itself.
# This should be handled gracefully, i.e. not introduce recursion
self._voltage = get_voltage()
return self._voltage
@voltage.setter
def voltage(self, value: float) -> None:
self._voltage = value
set_voltage(self._voltage)
class MyService(pydase.DataService):
def __init__(self) -> None:
super().__init__()
self.my_dict = {"key": OtherService()}
service = MyService()
pydase.Server(service)
# Changing the _voltage attribute should re-evaluate the voltage property, but avoid
# recursion
service.my_dict["key"].voltage = 1.2