Skip to content

Commit

Permalink
Merge pull request #197 from tiqi-group/fix/dict_key_normalization
Browse files Browse the repository at this point in the history
Fix: dict key normalization
  • Loading branch information
mosmuell authored Dec 20, 2024
2 parents 3c99f3f + 53a2a33 commit d6bad37
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 28 deletions.
4 changes: 1 addition & 3 deletions src/pydase/data_service/data_service_observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
)
from pydase.utils.helpers import (
get_object_attr_from_path,
normalize_full_access_path_string,
)
from pydase.utils.serialization.serializer import (
SerializationPathError,
Expand Down Expand Up @@ -102,8 +101,7 @@ def _update_cache_value(
)

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
Expand Down
2 changes: 1 addition & 1 deletion src/pydase/observer_pattern/observer/property_observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def _process_collection_item_properties(
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)
)
22 changes: 0 additions & 22 deletions src/pydase/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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('"', "'")
42 changes: 40 additions & 2 deletions tests/data_service/test_data_service_observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ def __init__(self) -> None:
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
Expand All @@ -184,3 +184,41 @@ def __init__(self) -> None:
)
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

0 comments on commit d6bad37

Please sign in to comment.