From 77ac6ca148fce8fe45dd8b867e24b1b30e15d710 Mon Sep 17 00:00:00 2001 From: sdb9696 Date: Sat, 29 Jun 2024 09:24:30 +0100 Subject: [PATCH] Backwards compatabilty --- kasa/device.py | 11 +++++- kasa/iot/iotdevice.py | 7 +++- kasa/iot/iotstrip.py | 30 ++++++++++++++- kasa/smart/smartchilddevice.py | 16 +++++++- kasa/smart/smartdevice.py | 16 +++++++- kasa/tests/test_childdevice.py | 69 ++++++++++++++++++++++++++++++++++ 6 files changed, 143 insertions(+), 6 deletions(-) diff --git a/kasa/device.py b/kasa/device.py index 822a5725..25b05856 100644 --- a/kasa/device.py +++ b/kasa/device.py @@ -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): diff --git a/kasa/iot/iotdevice.py b/kasa/iot/iotdevice.py index 21c06d2b..cb366b07 100755 --- a/kasa/iot/iotdevice.py +++ b/kasa/iot/iotdevice.py @@ -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`. diff --git a/kasa/iot/iotstrip.py b/kasa/iot/iotstrip.py index db74d100..f9a8ebad 100755 --- a/kasa/iot/iotstrip.py +++ b/kasa/iot/iotstrip.py @@ -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: diff --git a/kasa/smart/smartchilddevice.py b/kasa/smart/smartchilddevice.py index 4fce9d86..6f86eaf4 100644 --- a/kasa/smart/smartchilddevice.py +++ b/kasa/smart/smartchilddevice.py @@ -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: diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index eab428a0..d0fc1277 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -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( diff --git a/kasa/tests/test_childdevice.py b/kasa/tests/test_childdevice.py index de464560..e7ee764a 100644 --- a/kasa/tests/test_childdevice.py +++ b/kasa/tests/test_childdevice.py @@ -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()