Backwards compatabilty

This commit is contained in:
sdb9696 2024-06-29 09:24:30 +01:00
parent 00bf59be6c
commit 77ac6ca148
6 changed files with 143 additions and 6 deletions

View File

@ -233,7 +233,12 @@ class Device(ABC):
return await connect(host=host, config=config) # type: ignore[arg-type]
@abstractmethod
async def update(self, update_children_or_parent: bool = True):
async def update(
self,
update_children_or_parent: bool = True,
*,
update_children: bool | None = None,
):
"""Update the device.
Must be called after updating any attributes for the state changes
@ -244,6 +249,10 @@ class Device(ABC):
will also update the parent device. Setting to false requires
knowledge of the inner workings of the library and should be used
with caution.
:param update_children: Deprecated and provided for backwards
compatability. If provided update_children_or_parent will be
ignored. Child devices will not update parent devices whether
true or false.
"""
async def disconnect(self):

View File

@ -283,7 +283,12 @@ class IotDevice(Device):
"""Retrieve system information."""
return await self._query_helper("system", "get_sysinfo")
async def update(self, update_children_or_parent: bool = True):
async def update(
self,
update_children_or_parent: bool = True,
*,
update_children: bool | None = None,
):
"""Query the device to update the data.
Needed for properties that are decorated with `requires_update`.

View File

@ -6,6 +6,7 @@ import logging
from collections import defaultdict
from datetime import datetime, timedelta, timezone
from typing import Any
from warnings import warn
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
@ -121,11 +122,23 @@ class IotStrip(IotDevice):
"""Return if any of the outlets are on."""
return any(plug.is_on for plug in self.children)
async def update(self, update_children_or_parent: bool = True):
async def update(
self,
update_children_or_parent: bool = True,
*,
update_children: bool | None = None,
):
"""Update some of the attributes.
Needed for methods that are decorated with `requires_update`.
"""
if update_children is not None:
warn(
"update_children is deprecated, use update_children_or_parent",
DeprecationWarning,
stacklevel=1,
)
update_children_or_parent = update_children
await self._update(update_children_or_parent)
async def _update(
@ -368,11 +381,24 @@ class IotStripPlug(IotPlug):
for module_feat in module._module_features.values():
self._add_feature(module_feat)
async def update(self, update_children_or_parent: bool = True):
async def update(
self,
update_children_or_parent: bool = True,
*,
update_children: bool | None = None,
):
"""Query the device to update the data.
Needed for properties that are decorated with `requires_update`.
"""
if update_children is not None:
warn(
"update_children is deprecated, use update_children_or_parent",
DeprecationWarning,
stacklevel=1,
)
update_children_or_parent = False
if update_children_or_parent:
await self.parent._update(called_from_child=self)
else:

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import logging
from typing import Any
from warnings import warn
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
@ -36,12 +37,25 @@ class SmartChildDevice(SmartDevice):
self._id = info["device_id"]
self.protocol = _ChildProtocolWrapper(self._id, parent.protocol)
async def update(self, update_children_or_parent: bool = True):
async def update(
self,
update_children_or_parent: bool = True,
*,
update_children: bool | None = None,
):
"""Update the device.
Calling update directly on a child device will update the parent
and only this child.
"""
if update_children is not None:
warn(
"update_children is deprecated, use update_children_or_parent",
DeprecationWarning,
stacklevel=1,
)
update_children_or_parent = False
if update_children_or_parent:
await self._parent._update(called_from_child=self)
else:

View File

@ -7,6 +7,7 @@ import logging
from collections.abc import Mapping, Sequence
from datetime import datetime, timedelta, timezone
from typing import Any, cast
from warnings import warn
from ..aestransport import AesTransport
from ..device import Device, WifiNetwork
@ -147,8 +148,21 @@ class SmartDevice(Device):
if "child_device" in self._components and not self.children:
await self._initialize_children()
async def update(self, update_children_or_parent: bool = True):
async def update(
self,
update_children_or_parent: bool = True,
*,
update_children: bool | None = None,
):
"""Update the device."""
if update_children is not None:
warn(
"update_children is deprecated, use update_children_or_parent",
DeprecationWarning,
stacklevel=1,
)
update_children_or_parent = update_children
await self._update(update_children_or_parent)
async def _update(

View File

@ -114,6 +114,7 @@ async def test_parent_only_modules(dev, dummy_protocol, mocker):
@has_children
async def test_device_updates(dev: Device, mocker: MockerFixture):
"""Test usage of the update_children_or_parent parameter."""
if not dev.children and dev.device_type is Device.Type.Hub:
pytest.skip(f"Fixture for hub device {dev} does not have any children")
assert dev.children
@ -164,3 +165,71 @@ async def test_device_updates(dev: Device, mocker: MockerFixture):
child_spy.assert_called_once()
else:
child_spy.assert_not_called()
@pytest.mark.parametrize("update_children_or_parent", [True, False])
@has_children
async def test_device_updates_deprecated(
dev: Device, mocker: MockerFixture, update_children_or_parent
):
"""Test usage of the deprecated update_children parameter."""
# update_children_or_parent parameter ensures the value is ignored
if not dev.children and dev.device_type is Device.Type.Hub:
pytest.skip(f"Fixture for hub device {dev} does not have any children")
assert dev.children
parent_spy = mocker.spy(dev, "_update")
child_spies = {child: mocker.spy(child, "_update") for child in dev.children}
msg = "update_children is deprecated, use update_children_or_parent"
# update children, all devices call update
with pytest.deprecated_call(match=msg):
await dev.update(update_children_or_parent, update_children=True)
parent_spy.assert_called_once()
for child_spy in child_spies.values():
child_spy.assert_called_once()
# do not update children, only parent calls update
parent_spy.reset_mock()
for child_spy in child_spies.values():
child_spy.reset_mock()
with pytest.deprecated_call(match=msg):
await dev.update(update_children_or_parent, update_children=False)
parent_spy.assert_called_once()
for child_spy in child_spies.values():
child_spy.assert_not_called()
# on child update_children true
# only the child and no parent update
parent_spy.reset_mock()
for child_spy in child_spies.values():
child_spy.reset_mock()
child_to_update = dev.children[0]
with pytest.deprecated_call(match=msg):
await child_to_update.update(update_children_or_parent, update_children=True)
parent_spy.assert_not_called()
assert child_to_update
for child, child_spy in child_spies.items():
if child == child_to_update:
child_spy.assert_called_once()
else:
child_spy.assert_not_called()
# on child update_children false
# only the child and no parent update
parent_spy.reset_mock()
for child_spy in child_spies.values():
child_spy.reset_mock()
with pytest.deprecated_call(match=msg):
await child_to_update.update(update_children_or_parent, update_children=False)
parent_spy.assert_not_called()
assert child_to_update
for child, child_spy in child_spies.items():
if child == child_to_update:
child_spy.assert_called_once()
else:
child_spy.assert_not_called()