feat: added property callbacks, added warnings

This commit is contained in:
Mose Müller
2023-08-02 12:06:19 +02:00
parent 8e7568b57f
commit 206a831473
7 changed files with 549 additions and 41 deletions

View File

@ -1,8 +1,20 @@
from collections.abc import Generator
from typing import Any
import pytest
from loguru import logger
from pytest import LogCaptureFixture
from pyDataInterface import DataService
@pytest.fixture
def caplog(caplog: LogCaptureFixture) -> Generator[LogCaptureFixture, Any, None]:
handler_id = logger.add(caplog.handler, format="{message}")
yield caplog
logger.remove(handler_id)
def emit(self: Any, parent_path: str, name: str, value: Any) -> None:
if isinstance(value, DataService):
value = value.serialize()

View File

@ -5,30 +5,330 @@ from pyDataInterface import DataService
def test_properties(capsys: CaptureFixture) -> None:
class ServiceClass(DataService):
_power = True
_voltage = 10.0
_current = 1.0
@property
def power(self) -> bool:
return self._power
@power.setter
def power(self, value: bool) -> None:
self._power = value
def power(self) -> float:
return self._voltage * self.current
@property
def power_two(self) -> bool:
return self._power
def voltage(self) -> float:
return self._voltage
@voltage.setter
def voltage(self, value: float) -> None:
self._voltage = value
@property
def current(self) -> float:
return self._current
@current.setter
def current(self, value: float) -> None:
self._current = value
test_service = ServiceClass()
test_service.power = False
test_service.voltage = 1
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.power = False",
"ServiceClass.power_two = False",
"ServiceClass._power = False",
"ServiceClass.power = 1.0",
"ServiceClass.voltage = 1",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
test_service.current = 12.0
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.power = 12.0",
"ServiceClass.current = 12.0",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
def test_nested_properties(capsys: CaptureFixture) -> None:
class SubSubClass(DataService):
name = "Hello"
class SubClass(DataService):
name = "Hello"
class_attr = SubSubClass()
class ServiceClass(DataService):
class_attr = SubClass()
name = "World"
@property
def subsub_name(self) -> str:
return f"{self.class_attr.class_attr.name} {self.name}"
@property
def sub_name(self) -> str:
return f"{self.class_attr.name} {self.name}"
test_service = ServiceClass()
test_service.name = "Peepz"
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.name = Peepz",
"ServiceClass.sub_name = Hello Peepz",
"ServiceClass.subsub_name = Hello Peepz",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
test_service.class_attr.name = "Hi"
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.sub_name = Hi Peepz",
"ServiceClass.subsub_name = Hello Peepz", # registers subclass changes
"ServiceClass.class_attr.name = Hi",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
test_service.class_attr.class_attr.name = "Ciao"
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.sub_name = Hi Peepz", # registers subclass changes
"ServiceClass.subsub_name = Ciao Peepz",
"ServiceClass.class_attr.class_attr.name = Ciao",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
def test_simple_list_properties(capsys: CaptureFixture) -> None:
class ServiceClass(DataService):
list = ["Hello", "Ciao"]
name = "World"
@property
def total_name(self) -> str:
return f"{self.list[0]} {self.name}"
test_service = ServiceClass()
test_service.name = "Peepz"
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.name = Peepz",
"ServiceClass.total_name = Hello Peepz",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
test_service.list[0] = "Hi"
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.total_name = Hi Peepz",
"ServiceClass.list[0] = Hi",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
def test_class_list_properties(capsys: CaptureFixture) -> None:
class SubClass(DataService):
name = "Hello"
class ServiceClass(DataService):
list = [SubClass()]
name = "World"
@property
def total_name(self) -> str:
return f"{self.list[0].name} {self.name}"
test_service = ServiceClass()
test_service.name = "Peepz"
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.name = Peepz",
"ServiceClass.total_name = Hello Peepz",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
test_service.list[0].name = "Hi"
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.total_name = Hi Peepz",
"ServiceClass.list[0].name = Hi",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
def test_subclass_properties(capsys: CaptureFixture) -> None:
class SubClass(DataService):
name = "Hello"
_voltage = 10.0
_current = 1.0
@property
def power(self) -> float:
return self._voltage * self.current
@property
def voltage(self) -> float:
return self._voltage
@voltage.setter
def voltage(self, value: float) -> None:
self._voltage = value
@property
def current(self) -> float:
return self._current
@current.setter
def current(self, value: float) -> None:
self._current = value
class ServiceClass(DataService):
class_attr = SubClass()
test_service = ServiceClass()
test_service.class_attr.voltage = 10.0
captured = capsys.readouterr()
expected_output = sorted(
[
"ServiceClass.class_attr.voltage = 10.0",
"ServiceClass.class_attr.power = 10.0",
]
)
actual_output = sorted(captured.out.strip().split("\n"))
assert actual_output == expected_output
def test_subclass_properties(capsys: CaptureFixture) -> None:
class SubClass(DataService):
name = "Hello"
_voltage = 10.0
_current = 1.0
@property
def power(self) -> float:
return self._voltage * self.current
@property
def voltage(self) -> float:
return self._voltage
@voltage.setter
def voltage(self, value: float) -> None:
self._voltage = value
@property
def current(self) -> float:
return self._current
@current.setter
def current(self, value: float) -> None:
self._current = value
class ServiceClass(DataService):
class_attr = SubClass()
@property
def voltage(self) -> float:
return self.class_attr.voltage
test_service = ServiceClass()
test_service.class_attr.voltage = 10.0
captured = capsys.readouterr()
expected_output = sorted(
{
"ServiceClass.class_attr.voltage = 10.0",
"ServiceClass.class_attr.power = 10.0",
"ServiceClass.voltage = 10.0",
}
)
# using a set here as "ServiceClass.voltage = 10.0" is emitted twice. Once for
# changing voltage, and once for changing power.
actual_output = sorted(set(captured.out.strip().split("\n")))
assert actual_output == expected_output
def test_subclass_properties_2(capsys: CaptureFixture) -> None:
class SubClass(DataService):
name = "Hello"
_voltage = 10.0
_current = 1.0
@property
def power(self) -> float:
return self._voltage * self.current
@property
def voltage(self) -> float:
return self._voltage
@voltage.setter
def voltage(self, value: float) -> None:
self._voltage = value
@property
def current(self) -> float:
return self._current
@current.setter
def current(self, value: float) -> None:
self._current = value
class ServiceClass(DataService):
class_attr = [SubClass() for i in range(2)]
@property
def voltage(self) -> float:
return self.class_attr[0].voltage
test_service = ServiceClass()
test_service.class_attr[1].current = 10.0
captured = capsys.readouterr()
expected_output = sorted(
{
"ServiceClass.class_attr[1].current = 10.0",
"ServiceClass.class_attr[1].power = 100.0",
"ServiceClass.voltage = 10.0",
}
)
# using a set here as "ServiceClass.voltage = 10.0" is emitted twice. Once for
# changing current, and once for changing power. Note that the voltage property is
# only dependent on class_attr[0] but still emits an update notification. This is
# because every time any item in the list `test_service.class_attr` is changed,
# a notification will be emitted.
actual_output = sorted(set(captured.out.strip().split("\n")))
assert actual_output == expected_output

34
tests/test_warnings.py Normal file
View File

@ -0,0 +1,34 @@
from pytest import LogCaptureFixture
from pyDataInterface import DataService
from . import caplog # noqa
def test_setattr_warnings(caplog: LogCaptureFixture) -> None: # noqa
# def test_setattr_warnings(capsys: CaptureFixture) -> None:
class SubClass:
name = "Hello"
class ServiceClass(DataService):
def __init__(self) -> None:
self.attr_1 = SubClass()
super().__init__()
ServiceClass()
assert "Warning: Class SubClass does not inherit from DataService." in caplog.text
def test_private_attribute_warning(caplog: LogCaptureFixture) -> None: # noqa
class ServiceClass(DataService):
def __init__(self) -> None:
self.__something = ""
super().__init__()
ServiceClass()
assert (
" Warning: You should not set private but rather protected attributes! Use "
"_something instead of __something." in caplog.text
)