mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-04-20 08:20:02 +02:00
fix: property callback issues, implemented new tests
This commit is contained in:
parent
ae8be562db
commit
319a62bb01
@ -122,8 +122,20 @@ class DataService(rpyc.Service):
|
|||||||
)
|
)
|
||||||
if isinstance(attr_value, property):
|
if isinstance(attr_value, property):
|
||||||
dependencies = attr_value.fget.__code__.co_names # type: ignore
|
dependencies = attr_value.fget.__code__.co_names # type: ignore
|
||||||
|
source_code_string = inspect.getsource(attr_value.fget) # type: ignore
|
||||||
|
|
||||||
for dependency in dependencies:
|
for dependency in dependencies:
|
||||||
|
# check if the dependencies are attributes of obj
|
||||||
|
# This doesn't have to be the case like, for example, here:
|
||||||
|
# >>> @property
|
||||||
|
# >>> def power(self) -> float:
|
||||||
|
# >>> return self.class_attr.voltage * self.current
|
||||||
|
#
|
||||||
|
# The dependencies for this property are:
|
||||||
|
# ('class_attr', 'voltage', 'current')
|
||||||
|
if f"self.{dependency}" not in source_code_string:
|
||||||
|
continue
|
||||||
|
|
||||||
# use `obj` instead of `type(obj)` to get DataServiceList
|
# use `obj` instead of `type(obj)` to get DataServiceList
|
||||||
# instead of list
|
# instead of list
|
||||||
dependency_value = getattr(obj, dependency)
|
dependency_value = getattr(obj, dependency)
|
||||||
@ -347,7 +359,8 @@ class DataService(rpyc.Service):
|
|||||||
"""Handles registration of callbacks for DataService attributes"""
|
"""Handles registration of callbacks for DataService attributes"""
|
||||||
|
|
||||||
# as the DataService is an attribute of self, change the root object
|
# as the DataService is an attribute of self, change the root object
|
||||||
nested_attr.__root__ = self.__root__
|
# use the dictionary to not trigger callbacks on initialised objects
|
||||||
|
nested_attr.__dict__["__root__"] = self.__root__
|
||||||
|
|
||||||
new_path = f"{parent_path}.{attr_name}"
|
new_path = f"{parent_path}.{attr_name}"
|
||||||
self._register_DataService_callbacks(nested_attr, new_path)
|
self._register_DataService_callbacks(nested_attr, new_path)
|
||||||
|
@ -332,3 +332,96 @@ def test_subclass_properties_2(capsys: CaptureFixture) -> None:
|
|||||||
# a notification will be emitted.
|
# a notification will be emitted.
|
||||||
actual_output = sorted(set(captured.out.strip().split("\n")))
|
actual_output = sorted(set(captured.out.strip().split("\n")))
|
||||||
assert actual_output == expected_output
|
assert actual_output == expected_output
|
||||||
|
|
||||||
|
|
||||||
|
def test_subsubclass_properties(capsys: CaptureFixture) -> None:
|
||||||
|
class SubSubClass(DataService):
|
||||||
|
_voltage = 10.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def voltage(self) -> float:
|
||||||
|
return self._voltage
|
||||||
|
|
||||||
|
@voltage.setter
|
||||||
|
def voltage(self, value: float) -> None:
|
||||||
|
self._voltage = value
|
||||||
|
|
||||||
|
class SubClass(DataService):
|
||||||
|
class_attr = SubSubClass()
|
||||||
|
current = 0.5
|
||||||
|
|
||||||
|
@property
|
||||||
|
def power(self) -> float:
|
||||||
|
return self.class_attr.voltage * self.current
|
||||||
|
|
||||||
|
class ServiceClass(DataService):
|
||||||
|
class_attr = [SubClass() for i in range(2)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def power(self) -> float:
|
||||||
|
return self.class_attr[0].power
|
||||||
|
|
||||||
|
test_service = ServiceClass()
|
||||||
|
|
||||||
|
test_service.class_attr[1].class_attr.voltage = 100.0
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
expected_output = sorted(
|
||||||
|
{
|
||||||
|
"ServiceClass.class_attr[0].class_attr.voltage = 100.0",
|
||||||
|
"ServiceClass.class_attr[1].class_attr.voltage = 100.0",
|
||||||
|
"ServiceClass.class_attr[0].power = 50.0",
|
||||||
|
"ServiceClass.class_attr[1].power = 50.0",
|
||||||
|
"ServiceClass.power = 50.0",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
actual_output = sorted(set(captured.out.strip().split("\n")))
|
||||||
|
assert actual_output == expected_output
|
||||||
|
|
||||||
|
|
||||||
|
def test_subsubclass_instance_properties(capsys: CaptureFixture) -> None:
|
||||||
|
class SubSubClass(DataService):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._voltage = 10.0
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def voltage(self) -> float:
|
||||||
|
return self._voltage
|
||||||
|
|
||||||
|
@voltage.setter
|
||||||
|
def voltage(self, value: float) -> None:
|
||||||
|
self._voltage = value
|
||||||
|
|
||||||
|
class SubClass(DataService):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.attr = [SubSubClass()]
|
||||||
|
self.current = 0.5
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def power(self) -> float:
|
||||||
|
return self.attr[0].voltage * self.current
|
||||||
|
|
||||||
|
class ServiceClass(DataService):
|
||||||
|
class_attr = [SubClass() for i in range(2)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def power(self) -> float:
|
||||||
|
return self.class_attr[0].power
|
||||||
|
|
||||||
|
test_service = ServiceClass()
|
||||||
|
|
||||||
|
test_service.class_attr[1].attr[0].voltage = 100.0
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
# again, changing an item in a list will trigger the callbacks. This is why a
|
||||||
|
# notification for `ServiceClass.power` is emitted although it did not change its
|
||||||
|
# value
|
||||||
|
expected_output = sorted(
|
||||||
|
{
|
||||||
|
"ServiceClass.class_attr[1].attr[0].voltage = 100.0",
|
||||||
|
"ServiceClass.class_attr[1].power = 50.0",
|
||||||
|
"ServiceClass.power = 5.0",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
actual_output = sorted(set(captured.out.strip().split("\n")))
|
||||||
|
assert actual_output == expected_output
|
||||||
|
Loading…
x
Reference in New Issue
Block a user