From e560027a892a48c51bb96ce505508443fc089cbe Mon Sep 17 00:00:00 2001 From: Daniel Muehlbachler-Pietrzykowski Date: Thu, 14 Dec 2023 17:00:19 +0100 Subject: [PATCH] feat: perform deep merging of updates --- onyx_client/data/animation_value.py | 17 +++++++ onyx_client/data/boolean_value.py | 8 ++++ onyx_client/data/numeric_value.py | 7 +-- onyx_client/device/light.py | 16 ++----- onyx_client/device/shutter.py | 12 ++--- onyx_client/device/weather.py | 26 +++------- tests/data/test_animation_value.py | 74 +++++++++++++++++++++++++++++ tests/data/test_boolean_value.py | 18 +++++++ tests/device/test_light.py | 33 +++++++++++++ tests/device/test_shutter.py | 38 +++++++++++++++ tests/device/test_weather.py | 48 +++++++++++++++++++ 11 files changed, 252 insertions(+), 45 deletions(-) diff --git a/onyx_client/data/animation_value.py b/onyx_client/data/animation_value.py index 7f99b7d..649c769 100644 --- a/onyx_client/data/animation_value.py +++ b/onyx_client/data/animation_value.py @@ -1,4 +1,6 @@ """Animation Values of Onyx devices.""" +from typing import Optional + from onyx_client.data.animation_keyframe import AnimationKeyframe @@ -35,6 +37,21 @@ def create(properties: dict): ], ) + def update_with(self, other: Optional): + """Updates this value with the target. + + other: the other value""" + if other is not None: + self.start = self.start if other.start is None else other.start + self.current_value = ( + self.current_value + if other.current_value is None + else other.current_value + ) + self.keyframes = ( + self.keyframes if other.keyframes is None else other.keyframes + ) + def __eq__(self, other): if isinstance(self, other.__class__): return ( diff --git a/onyx_client/data/boolean_value.py b/onyx_client/data/boolean_value.py index 5805c4b..79fac38 100644 --- a/onyx_client/data/boolean_value.py +++ b/onyx_client/data/boolean_value.py @@ -1,4 +1,5 @@ """Boolean Values of Onyx devices.""" +from typing import Optional class BooleanValue: @@ -25,6 +26,13 @@ def create(properties: dict): properties.get("read_only", "false") == "true", ) + def update_with(self, other: Optional): + """Updates this value with the target. + + other: the other value""" + if other is not None: + self.value = self.value if other.value is None else other.value + def __str__(self) -> str: return f"BooleanValue(value={self.value})" diff --git a/onyx_client/data/numeric_value.py b/onyx_client/data/numeric_value.py index 4b08939..45182c5 100644 --- a/onyx_client/data/numeric_value.py +++ b/onyx_client/data/numeric_value.py @@ -58,9 +58,10 @@ def update_with(self, other: Optional): self.read_only = ( self.read_only if other.read_only is None else other.read_only ) - self.animation = ( - self.animation if other.animation is None else other.animation - ) + if self.animation is not None: + self.animation.update_with(other.animation) + else: + self.animation = other.animation def __eq__(self, other): if isinstance(self, other.__class__): diff --git a/onyx_client/device/light.py b/onyx_client/device/light.py index c23dc27..cf0b9fc 100644 --- a/onyx_client/device/light.py +++ b/onyx_client/device/light.py @@ -43,19 +43,9 @@ def update_with(self, update): update: the update patch""" super().update_with(update) - self.target_brightness = ( - self.target_brightness - if update.target_brightness is None - else update.target_brightness - ) - self.actual_brightness = ( - self.actual_brightness - if update.actual_brightness is None - else update.actual_brightness - ) - self.dim_duration = ( - self.dim_duration if update.dim_duration is None else update.dim_duration - ) + self.target_brightness.update_with(update.target_brightness) + self.actual_brightness.update_with(update.actual_brightness) + self.dim_duration.update_with(update.dim_duration) @staticmethod def keys() -> list: diff --git a/onyx_client/device/shutter.py b/onyx_client/device/shutter.py index a166cee..fddd2a9 100644 --- a/onyx_client/device/shutter.py +++ b/onyx_client/device/shutter.py @@ -62,16 +62,10 @@ def update_with(self, update): update: the update patch""" super().update_with(update) - self.target_position = ( - self.target_position - if update.target_position is None - else update.target_position - ) - self.target_angle = ( - self.target_angle if update.target_angle is None else update.target_angle - ) - self.actual_angle.update_with(update.actual_angle) + self.target_position.update_with(update.target_position) + self.target_angle.update_with(update.target_angle) self.actual_position.update_with(update.actual_position) + self.actual_angle.update_with(update.actual_angle) @staticmethod def keys() -> list: diff --git a/onyx_client/device/weather.py b/onyx_client/device/weather.py index f874675..7ef8c81 100644 --- a/onyx_client/device/weather.py +++ b/onyx_client/device/weather.py @@ -53,26 +53,12 @@ def update_with(self, update): update: the update patch""" super().update_with(update) - self.wind_peak = ( - self.wind_peak if update.wind_peak is None else update.wind_peak - ) - self.sun_brightness_peak = ( - self.sun_brightness_peak - if update.sun_brightness_peak is None - else update.sun_brightness_peak - ) - self.sun_brightness_sink = ( - self.sun_brightness_sink - if update.sun_brightness_sink is None - else update.sun_brightness_sink - ) - self.air_pressure = ( - self.air_pressure if update.air_pressure is None else update.air_pressure - ) - self.humidity = self.humidity if update.humidity is None else update.humidity - self.temperature = ( - self.temperature if update.temperature is None else update.temperature - ) + self.wind_peak.update_with(update.wind_peak) + self.sun_brightness_peak.update_with(update.sun_brightness_peak) + self.sun_brightness_sink.update_with(update.sun_brightness_sink) + self.air_pressure.update_with(update.air_pressure) + self.humidity.update_with(update.humidity) + self.temperature.update_with(update.temperature) @staticmethod def keys() -> list: diff --git a/tests/data/test_animation_value.py b/tests/data/test_animation_value.py index a4bf970..85cd4b5 100644 --- a/tests/data/test_animation_value.py +++ b/tests/data/test_animation_value.py @@ -68,3 +68,77 @@ def test_str(self): def test_not_eq(self): assert AnimationValue(10, 10, list()) != 10 + + def test_update_with(self): + value1 = AnimationValue(1.0, 1, [AnimationKeyframe("linear", 0, 1.0, 1)]) + value2 = AnimationValue( + 2.0, + 2, + [ + AnimationKeyframe("linear", 1, 2.0, 2), + AnimationKeyframe("linear", 2, 3.0, 3), + ], + ) + value1.update_with(value2) + assert value1.start == 2.0 + assert value1.current_value == 2 + assert len(value1.keyframes) == 2 + assert value1.keyframes[0].delay == 1 + assert value1.keyframes[0].duration == 2.0 + assert value1.keyframes[0].value == 2 + + def test_update_with_none(self): + value1 = AnimationValue(1.0, 1, [AnimationKeyframe("linear", 0, 1.0, 1)]) + value2 = None + value1.update_with(value2) + assert value1.start == 1.0 + assert value1.current_value == 1 + assert len(value1.keyframes) == 1 + assert value1.keyframes[0].delay == 0 + assert value1.keyframes[0].duration == 1.0 + assert value1.keyframes[0].value == 1 + + def test_update_with_partials(self): + value1 = AnimationValue(1.0, 1, [AnimationKeyframe("linear", 0, 1.0, 1)]) + value2 = AnimationValue( + None, + 2, + [ + AnimationKeyframe("linear", 1, 2.0, 2), + AnimationKeyframe("linear", 2, 3.0, 3), + ], + ) + value1.update_with(value2) + assert value1.start == 1.0 + assert value1.current_value == 2 + assert len(value1.keyframes) == 2 + assert value1.keyframes[0].delay == 1 + assert value1.keyframes[0].duration == 2.0 + assert value1.keyframes[0].value == 2 + + value1 = AnimationValue(1.0, 1, [AnimationKeyframe("linear", 0, 1.0, 1)]) + value2 = AnimationValue( + 2.0, + None, + [ + AnimationKeyframe("linear", 1, 2.0, 2), + AnimationKeyframe("linear", 2, 3.0, 3), + ], + ) + value1.update_with(value2) + assert value1.start == 2.0 + assert value1.current_value == 1 + assert len(value1.keyframes) == 2 + assert value1.keyframes[0].delay == 1 + assert value1.keyframes[0].duration == 2.0 + assert value1.keyframes[0].value == 2 + + value1 = AnimationValue(1.0, 1, [AnimationKeyframe("linear", 0, 1.0, 1)]) + value2 = AnimationValue(2.0, 2, None) + value1.update_with(value2) + assert value1.start == 2.0 + assert value1.current_value == 2 + assert len(value1.keyframes) == 1 + assert value1.keyframes[0].delay == 0 + assert value1.keyframes[0].duration == 1.0 + assert value1.keyframes[0].value == 1 diff --git a/tests/data/test_boolean_value.py b/tests/data/test_boolean_value.py index e4655ec..3370736 100644 --- a/tests/data/test_boolean_value.py +++ b/tests/data/test_boolean_value.py @@ -39,3 +39,21 @@ def test_str(self): def test_not_eq(self): assert BooleanValue(True, True) != 10 + + def test_update_with_true(self): + value1 = BooleanValue(False, False) + value2 = BooleanValue(True, False) + value1.update_with(value2) + assert value1.value + + def test_update_with_false(self): + value1 = BooleanValue(True, False) + value2 = BooleanValue(False, False) + value1.update_with(value2) + assert not value1.value + + def test_update_with_none(self): + value1 = BooleanValue(True, False) + value2 = BooleanValue(None, False) + value1.update_with(value2) + assert value1.value diff --git a/tests/device/test_light.py b/tests/device/test_light.py index c248357..87db131 100644 --- a/tests/device/test_light.py +++ b/tests/device/test_light.py @@ -150,3 +150,36 @@ def test_update_with_exception(self, device_mode): ) with pytest.raises(UpdateException): light.update_with(update) + + def test_update_with_partials(self, device_mode): + value1 = NumericValue(1, 0, 10, False) + value2 = NumericValue(2, 0, 10, False) + value3 = NumericValue(3, 0, 10, False) + value4 = NumericValue(None, 10, None, False) + value5 = NumericValue(20, None, None, False) + value6 = NumericValue(None, None, 100, False) + light = Light( + "id", + "name", + DeviceType.BASIC_LIGHT, + device_mode, + list(Action), + value1, + value2, + value3, + ) + update = Light( + "id", + "name1", + DeviceType.BASIC_LIGHT, + device_mode, + list(Action), + value4, + value5, + value6, + ) + light.update_with(update) + assert light.name == "name1" + assert light.target_brightness == NumericValue(1, 10, 10, False) + assert light.actual_brightness == NumericValue(20, 0, 10, False) + assert light.dim_duration == NumericValue(3, 0, 100, False) diff --git a/tests/device/test_shutter.py b/tests/device/test_shutter.py index f06c92b..264558b 100644 --- a/tests/device/test_shutter.py +++ b/tests/device/test_shutter.py @@ -181,3 +181,41 @@ def test_update_with_exception(self, device_mode): ) with pytest.raises(UpdateException): shutter.update_with(update) + + def test_update_with_partials(self, device_mode): + value1 = NumericValue(1, 0, 10, False) + value2 = NumericValue(2, 0, 10, False) + value3 = NumericValue(3, 0, 10, False) + value4 = NumericValue(4, 0, 10, False) + value5 = NumericValue(10, None, None, False) + value6 = NumericValue(None, 10, None, False) + value7 = NumericValue(None, None, 100, False) + value8 = NumericValue(None, None, None, False) + shutter = Shutter( + "id", + "name", + DeviceType.AWNING, + device_mode, + list(Action), + value1, + value2, + value3, + value4, + ) + update = Shutter( + "id", + "name1", + DeviceType.AWNING, + device_mode, + list(Action), + value5, + value6, + value7, + value8, + ) + shutter.update_with(update) + assert shutter.name == "name1" + assert shutter.target_position == NumericValue(10, 0, 10, False) + assert shutter.target_angle == NumericValue(2, 10, 10, False) + assert shutter.actual_angle == NumericValue(3, 0, 100, False) + assert shutter.actual_position == NumericValue(4, 0, 10, False) diff --git a/tests/device/test_weather.py b/tests/device/test_weather.py index 9de8f37..ed6fee4 100644 --- a/tests/device/test_weather.py +++ b/tests/device/test_weather.py @@ -195,3 +195,51 @@ def test_update_with_exception(self, device_mode): ) with pytest.raises(UpdateException): weather.update_with(update) + + def test_update_with_partials(self, device_mode): + value1 = NumericValue(1, 0, 10, False) + value2 = NumericValue(2, 0, 10, False) + value3 = NumericValue(3, 0, 10, False) + value4 = NumericValue(4, 0, 10, False) + value5 = NumericValue(5, 0, 10, False) + value6 = NumericValue(6, 0, 10, False) + value7 = NumericValue(10, None, None, False) + value8 = NumericValue(None, 10, None, False) + value9 = NumericValue(None, None, 100, False) + value10 = NumericValue(40, None, None, False) + value11 = NumericValue(None, 10, None, False) + value12 = NumericValue(None, None, 100, False) + weather = Weather( + "id", + "name", + DeviceType.WEATHER, + device_mode, + list(Action), + value1, + value2, + value3, + value4, + value5, + value6, + ) + update = Weather( + "id", + "name1", + DeviceType.WEATHER, + device_mode, + list(Action), + value7, + value8, + value9, + value10, + value11, + value12, + ) + weather.update_with(update) + assert weather.name == "name1" + assert weather.wind_peak == NumericValue(10, 0, 10, False) + assert weather.sun_brightness_peak == NumericValue(2, 10, 10, False) + assert weather.sun_brightness_sink == NumericValue(3, 0, 100, False) + assert weather.air_pressure == NumericValue(40, 0, 10, False) + assert weather.humidity == NumericValue(5, 10, 10, False) + assert weather.temperature == NumericValue(6, 0, 100, False)