Improve supported module checks for hub children (#1188)

No devices in `fixtures/smart/child` support the `get_device_time` or
`get_device_usage` methods so this PR tests for whether the device is a
hub child and marks those modules/methods as not supported. This
prevents features being erroneously created on child devices.

It also moves the logic for getting the time from the parent module
behind getting it from the child module which was masking the creation
of these unsupported modules.
This commit is contained in:
Steven B. 2024-10-23 16:17:27 +01:00 committed by GitHub
parent 048c84d72c
commit cd0a74ca96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 32 additions and 3 deletions

View File

@ -23,7 +23,8 @@ class DeviceModule(SmartModule):
"get_device_info": None, "get_device_info": None,
} }
# Device usage is not available on older firmware versions # Device usage is not available on older firmware versions
if self.supported_version >= 2: # or child devices of hubs
if self.supported_version >= 2 and not self._device._is_hub_child:
query["get_device_usage"] = None query["get_device_usage"] = None
return query return query

View File

@ -83,3 +83,12 @@ class Time(SmartModule, TimeInterface):
if region: if region:
params["region"] = region params["region"] = region
return await self.call("set_device_time", params) return await self.call("set_device_time", params)
async def _check_supported(self):
"""Additional check to see if the module is supported by the device.
Hub attached sensors report the time module but do return device time.
"""
if self._device._is_hub_child:
return False
return await super()._check_supported()

View File

@ -457,6 +457,11 @@ class SmartDevice(Device):
for child in self._children.values(): for child in self._children.values():
await child._initialize_features() await child._initialize_features()
@property
def _is_hub_child(self) -> bool:
"""Returns true if the device is a child of a hub."""
return self.parent is not None and self.parent.device_type is DeviceType.Hub
@property @property
def is_cloud_connected(self) -> bool: def is_cloud_connected(self) -> bool:
"""Returns if the device is connected to the cloud.""" """Returns if the device is connected to the cloud."""
@ -485,8 +490,8 @@ class SmartDevice(Device):
@property @property
def time(self) -> datetime: def time(self) -> datetime:
"""Return the time.""" """Return the time."""
if (self._parent and (time_mod := self._parent.modules.get(Module.Time))) or ( if (time_mod := self.modules.get(Module.Time)) or (
time_mod := self.modules.get(Module.Time) self._parent and (time_mod := self._parent.modules.get(Module.Time))
): ):
return time_mod.time return time_mod.time

View File

@ -1,7 +1,9 @@
import inspect import inspect
import sys import sys
from datetime import datetime, timezone
import pytest import pytest
from freezegun.api import FrozenDateTimeFactory
from kasa import Device from kasa import Device
from kasa.device_type import DeviceType from kasa.device_type import DeviceType
@ -120,3 +122,15 @@ async def test_parent_property(dev: Device):
assert dev.parent is None assert dev.parent is None
for child in dev.children: for child in dev.children:
assert child.parent == dev assert child.parent == dev
@has_children_smart
async def test_child_time(dev: Device, freezer: FrozenDateTimeFactory):
"""Test a child device gets the time from it's parent module."""
if not dev.children:
pytest.skip(f"Device {dev} fixture does not have any children")
fallback_time = datetime.now(timezone.utc).astimezone().replace(microsecond=0)
assert dev.parent is None
for child in dev.children:
assert child.time != fallback_time