updates _ObservableDict

- allows for strings and numbers now
- key will have double quotes (") instead of single quote (') when key is a string
- fixed some few things
- added/updated tests
This commit is contained in:
Mose Müller 2024-04-30 07:25:42 +02:00
parent e9a7e785dd
commit bb3d6fcce1
2 changed files with 61 additions and 31 deletions

View File

@ -3,6 +3,8 @@ from abc import ABC, abstractmethod
from collections.abc import Iterable
from typing import TYPE_CHECKING, Any, ClassVar, SupportsIndex
from pydase.utils.helpers import parse_serialized_key
if TYPE_CHECKING:
from pydase.observer_pattern.observer.observer import Observer
@ -81,7 +83,7 @@ class ObservableObject(ABC):
)
observer._notify_change_start(extended_attr_path)
def _initialise_new_objects(self, attr_name_or_key: Any, value: Any) -> Any:
def _initialise_new_objects(self, attr_name_or_key: str, value: Any) -> Any:
new_value = value
if isinstance(value, list):
if id(value) in self._list_mapping:
@ -100,7 +102,7 @@ class ObservableObject(ABC):
new_value = _ObservableDict(original_dict=value)
self._dict_mapping[id(value)] = new_value
if isinstance(new_value, ObservableObject):
new_value.add_observer(self, str(attr_name_or_key))
new_value.add_observer(self, attr_name_or_key)
return new_value
@abstractmethod
@ -224,33 +226,44 @@ class _ObservableList(ObservableObject, list[Any]):
return instance_attr_name
class _ObservableDict(dict[str, Any], ObservableObject):
class _ObservableDict(dict[str | float, Any], ObservableObject):
def __init__(
self,
original_dict: dict[str, Any],
original_dict: dict[str | float, Any],
) -> None:
self._original_dict = original_dict
ObservableObject.__init__(self)
dict.__init__(self)
for key, value in self._original_dict.items():
super().__setitem__(key, self._initialise_new_objects(f"['{key}']", value))
observer_key = key if not isinstance(key, str) else f'"{key}"'
super().__setitem__(
key, self._initialise_new_objects(f"[{observer_key}]", value)
)
def __setitem__(self, key: str, value: Any) -> None:
if not isinstance(key, str):
logger.warning("Converting non-string dictionary key %s to string.", key)
def __setitem__(self, key: str | float, value: Any) -> None:
if not isinstance(key, str | int | float):
logger.warning(
"Dictionary key %s is neither string nor number. Converting to string"
"...",
key,
)
key = str(key)
observer_key = key
if isinstance(key, str):
observer_key = f'"{key}"'
if hasattr(self, "_observers"):
self._remove_observer_if_observable(f"['{key}']")
value = self._initialise_new_objects(key, value)
self._notify_change_start(f"['{key}']")
self._remove_observer_if_observable(f"[{observer_key}]")
value = self._initialise_new_objects(f"[{observer_key}]", value)
self._notify_change_start(f"[{observer_key}]")
super().__setitem__(key, value)
self._notify_changed(f"['{key}']", value)
self._notify_changed(f"[{observer_key}]", value)
def _remove_observer_if_observable(self, name: str) -> None:
key = name[2:-2]
key = parse_serialized_key(name)
current_value = self.get(key, None)
if isinstance(current_value, ObservableObject):

View File

@ -80,8 +80,8 @@ def test_simple_instance_dict_attribute(caplog: pytest.LogCaptureFixture) -> Non
instance.dict_attr["first"] = "Ciao"
instance.dict_attr["second"] = "World"
assert "'dict_attr['first']' changed to 'Ciao'" in caplog.text
assert "'dict_attr['second']' changed to 'World'" in caplog.text
assert "'dict_attr[\"first\"]' changed to 'Ciao'" in caplog.text
assert "'dict_attr[\"second\"]' changed to 'World'" in caplog.text
def test_simple_class_dict_attribute(caplog: pytest.LogCaptureFixture) -> None:
@ -93,8 +93,8 @@ def test_simple_class_dict_attribute(caplog: pytest.LogCaptureFixture) -> None:
instance.dict_attr["first"] = "Ciao"
instance.dict_attr["second"] = "World"
assert "'dict_attr['first']' changed to 'Ciao'" in caplog.text
assert "'dict_attr['second']' changed to 'World'" in caplog.text
assert "'dict_attr[\"first\"]' changed to 'Ciao'" in caplog.text
assert "'dict_attr[\"second\"]' changed to 'World'" in caplog.text
def test_instance_dict_attribute(caplog: pytest.LogCaptureFixture) -> None:
@ -112,7 +112,7 @@ def test_instance_dict_attribute(caplog: pytest.LogCaptureFixture) -> None:
MyObserver(instance)
instance.dict_attr["first"].name = "Ciao"
assert "'dict_attr['first'].name' changed to 'Ciao'" in caplog.text
assert "'dict_attr[\"first\"].name' changed to 'Ciao'" in caplog.text
def test_class_dict_attribute(caplog: pytest.LogCaptureFixture) -> None:
@ -126,7 +126,7 @@ def test_class_dict_attribute(caplog: pytest.LogCaptureFixture) -> None:
MyObserver(instance)
instance.dict_attr["first"].name = "Ciao"
assert "'dict_attr['first'].name' changed to 'Ciao'" in caplog.text
assert "'dict_attr[\"first\"].name' changed to 'Ciao'" in caplog.text
def test_removed_observer_on_class_list_attr(caplog: pytest.LogCaptureFixture) -> None:
@ -166,19 +166,28 @@ def test_removed_observer_on_instance_dict_attr(
def __init__(self) -> None:
super().__init__()
self.nested_attr = nested_instance
self.changed_dict_attr = {"nested": nested_instance}
self.changed_dict_attr = {"nested": nested_instance, 2.1: nested_instance}
instance = MyObservable()
MyObserver(instance)
instance.changed_dict_attr["nested"] = "Ciao"
instance.changed_dict_attr[2.1] = "foo"
assert "'changed_dict_attr['nested']' changed to 'Ciao'" in caplog.text
assert "'changed_dict_attr[\"nested\"]' changed to 'Ciao'" in caplog.text
assert "'changed_dict_attr[2.1]' changed to 'foo'" in caplog.text
caplog.clear()
assert nested_instance._observers == {
'["nested"]': [],
"[2.1]": [],
"nested_attr": [instance],
}
instance.nested_attr.name = "Hi"
assert "'nested_attr.name' changed to 'Hi'" in caplog.text
assert "'changed_dict_attr['nested'].name' changed to 'Hi'" not in caplog.text
assert "'changed_dict_attr[\"nested\"].name' changed to 'Hi'" not in caplog.text
assert "'changed_dict_attr[2.1].name' changed to 'Hi'" not in caplog.text
def test_removed_observer_on_instance_list_attr(
@ -217,24 +226,32 @@ def test_removed_observer_on_class_dict_attr(caplog: pytest.LogCaptureFixture) -
self.name = "Hello"
nested_instance = NestedObservable()
assert nested_instance._observers == {}
class MyObservable(Observable):
def __init__(self) -> None:
super().__init__()
self.nested_attr = nested_instance
self.changed_dict_attr = {"nested": nested_instance}
nested_attr = nested_instance
changed_dict_attr = {"nested": nested_instance, 2.1: nested_instance}
instance = MyObservable()
MyObserver(instance)
instance.changed_dict_attr["nested"] = "Ciao"
instance.changed_dict_attr[2.1] = "foo"
assert "'changed_dict_attr['nested']' changed to 'Ciao'" in caplog.text
assert "'changed_dict_attr[\"nested\"]' changed to 'Ciao'" in caplog.text
assert "'changed_dict_attr[2.1]' changed to 'foo'" in caplog.text
caplog.clear()
assert nested_instance._observers == {
'["nested"]': [],
"[2.1]": [],
"nested_attr": [instance],
}
instance.nested_attr.name = "Hi"
assert "'nested_attr.name' changed to 'Hi'" in caplog.text
assert "'changed_dict_attr['nested'].name' changed to 'Hi'" not in caplog.text
assert "'changed_dict_attr[\"nested\"].name' changed to 'Hi'" not in caplog.text
assert "'changed_dict_attr[2.1].name' changed to 'Hi'" not in caplog.text
def test_nested_dict_instances(caplog: pytest.LogCaptureFixture) -> None:
@ -249,7 +266,7 @@ def test_nested_dict_instances(caplog: pytest.LogCaptureFixture) -> None:
MyObserver(instance)
instance.nested_dict_attr["nested"]["first"] = "Ciao"
assert "'nested_dict_attr['nested']['first']' changed to 'Ciao'" in caplog.text
assert "'nested_dict_attr[\"nested\"][\"first\"]' changed to 'Ciao'" in caplog.text
def test_dict_in_list_instance(caplog: pytest.LogCaptureFixture) -> None:
@ -264,7 +281,7 @@ def test_dict_in_list_instance(caplog: pytest.LogCaptureFixture) -> None:
MyObserver(instance)
instance.dict_in_list[0]["first"] = "Ciao"
assert "'dict_in_list[0]['first']' changed to 'Ciao'" in caplog.text
assert "'dict_in_list[0][\"first\"]' changed to 'Ciao'" in caplog.text
def test_list_in_dict_instance(caplog: pytest.LogCaptureFixture) -> None:
@ -279,7 +296,7 @@ def test_list_in_dict_instance(caplog: pytest.LogCaptureFixture) -> None:
MyObserver(instance)
instance.list_in_dict["some_list"][0] = "Ciao"
assert "'list_in_dict['some_list'][0]' changed to 'Ciao'" in caplog.text
assert "'list_in_dict[\"some_list\"][0]' changed to 'Ciao'" in caplog.text
def test_list_append(caplog: pytest.LogCaptureFixture) -> None: