mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 10:44:04 +00:00
Create common interfaces for remaining device types (#895)
Introduce common module interfaces across smart and iot devices and provide better typing implementation for getting modules to support this.
This commit is contained in:
@@ -189,6 +189,11 @@ class FakeSmartTransport(BaseTransport):
|
||||
if "current_rule_id" in info["get_dynamic_light_effect_rules"]:
|
||||
del info["get_dynamic_light_effect_rules"]["current_rule_id"]
|
||||
|
||||
def _set_led_info(self, info, params):
|
||||
"""Set or remove values as per the device behaviour."""
|
||||
info["get_led_info"]["led_status"] = params["led_rule"] != "never"
|
||||
info["get_led_info"]["led_rule"] = params["led_rule"]
|
||||
|
||||
def _send_request(self, request_dict: dict):
|
||||
method = request_dict["method"]
|
||||
params = request_dict["params"]
|
||||
@@ -218,7 +223,9 @@ class FakeSmartTransport(BaseTransport):
|
||||
# SMART fixtures started to be generated
|
||||
missing_result := self.FIXTURE_MISSING_MAP.get(method)
|
||||
) and missing_result[0] in self.components:
|
||||
result = copy.deepcopy(missing_result[1])
|
||||
# Copy to info so it will work with update methods
|
||||
info[method] = copy.deepcopy(missing_result[1])
|
||||
result = copy.deepcopy(info[method])
|
||||
retval = {"result": result, "error_code": 0}
|
||||
else:
|
||||
# PARAMS error returned for KS240 when get_device_usage called
|
||||
@@ -239,6 +246,9 @@ class FakeSmartTransport(BaseTransport):
|
||||
elif method == "set_dynamic_light_effect_rule_enable":
|
||||
self._set_light_effect(info, params)
|
||||
return {"error_code": 0}
|
||||
elif method == "set_led_info":
|
||||
self._set_led_info(info, params)
|
||||
return {"error_code": 0}
|
||||
elif method[:4] == "set_":
|
||||
target_method = f"get_{method[4:]}"
|
||||
info[target_method].update(params)
|
||||
|
@@ -10,7 +10,7 @@ brightness = parametrize("brightness smart", component_filter="brightness")
|
||||
@brightness
|
||||
async def test_brightness_component(dev: SmartDevice):
|
||||
"""Test brightness feature."""
|
||||
brightness = dev.get_module("Brightness")
|
||||
brightness = dev.modules.get("Brightness")
|
||||
assert brightness
|
||||
assert isinstance(dev, SmartDevice)
|
||||
assert "brightness" in dev._components
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from kasa import SmartDevice
|
||||
from kasa.smart.modules import ContactSensor
|
||||
from kasa import Module, SmartDevice
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
contact = parametrize(
|
||||
@@ -18,7 +17,7 @@ contact = parametrize(
|
||||
)
|
||||
async def test_contact_features(dev: SmartDevice, feature, type):
|
||||
"""Test that features are registered and work as expected."""
|
||||
contact = dev.get_module(ContactSensor)
|
||||
contact = dev.modules.get(Module.ContactSensor)
|
||||
assert contact is not None
|
||||
|
||||
prop = getattr(contact, feature)
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Module
|
||||
from kasa.smart import SmartDevice
|
||||
from kasa.smart.modules import FanModule
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"SMART"})
|
||||
@@ -11,7 +11,7 @@ fan = parametrize("has fan", component_filter="fan_control", protocol_filter={"S
|
||||
@fan
|
||||
async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test fan speed feature."""
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
|
||||
level_feature = fan._module_features["fan_speed_level"]
|
||||
@@ -36,7 +36,7 @@ async def test_fan_speed(dev: SmartDevice, mocker: MockerFixture):
|
||||
@fan
|
||||
async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test sleep mode feature."""
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
sleep_feature = fan._module_features["fan_sleep_mode"]
|
||||
assert isinstance(sleep_feature.value, bool)
|
||||
@@ -55,7 +55,7 @@ async def test_sleep_mode(dev: SmartDevice, mocker: MockerFixture):
|
||||
async def test_fan_interface(dev: SmartDevice, mocker: MockerFixture):
|
||||
"""Test fan speed on device interface."""
|
||||
assert isinstance(dev, SmartDevice)
|
||||
fan = dev.get_module(FanModule)
|
||||
fan = dev.modules.get(Module.Fan)
|
||||
assert fan
|
||||
device = fan._device
|
||||
assert device.is_fan
|
||||
|
@@ -6,8 +6,8 @@ import logging
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Module
|
||||
from kasa.smart import SmartDevice
|
||||
from kasa.smart.modules import Firmware
|
||||
from kasa.smart.modules.firmware import DownloadState
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
@@ -31,7 +31,7 @@ async def test_firmware_features(
|
||||
dev: SmartDevice, feature, prop_name, type, required_version, mocker: MockerFixture
|
||||
):
|
||||
"""Test light effect."""
|
||||
fw = dev.get_module(Firmware)
|
||||
fw = dev.modules.get(Module.Firmware)
|
||||
assert fw
|
||||
|
||||
if not dev.is_cloud_connected:
|
||||
@@ -51,7 +51,7 @@ async def test_firmware_features(
|
||||
@firmware
|
||||
async def test_update_available_without_cloud(dev: SmartDevice):
|
||||
"""Test that update_available returns None when disconnected."""
|
||||
fw = dev.get_module(Firmware)
|
||||
fw = dev.modules.get(Module.Firmware)
|
||||
assert fw
|
||||
|
||||
if dev.is_cloud_connected:
|
||||
@@ -67,7 +67,7 @@ async def test_firmware_update(
|
||||
"""Test updating firmware."""
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
fw = dev.get_module(Firmware)
|
||||
fw = dev.modules.get(Module.Firmware)
|
||||
assert fw
|
||||
|
||||
upgrade_time = 5
|
||||
|
@@ -1,12 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import chain
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Device, Feature
|
||||
from kasa import Device, Feature, Module
|
||||
from kasa.smart.modules import LightEffectModule
|
||||
from kasa.tests.device_fixtures import parametrize
|
||||
|
||||
@@ -18,8 +17,8 @@ light_effect = parametrize(
|
||||
@light_effect
|
||||
async def test_light_effect(dev: Device, mocker: MockerFixture):
|
||||
"""Test light effect."""
|
||||
light_effect = cast(LightEffectModule, dev.modules.get("LightEffectModule"))
|
||||
assert light_effect
|
||||
light_effect = dev.modules.get(Module.LightEffect)
|
||||
assert isinstance(light_effect, LightEffectModule)
|
||||
|
||||
feature = light_effect._module_features["light_effect"]
|
||||
assert feature.type == Feature.Type.Choice
|
||||
|
95
kasa/tests/test_common_modules.py
Normal file
95
kasa/tests/test_common_modules.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Device, Module
|
||||
from kasa.tests.device_fixtures import (
|
||||
lightstrip,
|
||||
parametrize,
|
||||
parametrize_combine,
|
||||
plug_iot,
|
||||
)
|
||||
|
||||
led_smart = parametrize(
|
||||
"has led smart", component_filter="led", protocol_filter={"SMART"}
|
||||
)
|
||||
led = parametrize_combine([led_smart, plug_iot])
|
||||
|
||||
light_effect_smart = parametrize(
|
||||
"has light effect smart", component_filter="light_effect", protocol_filter={"SMART"}
|
||||
)
|
||||
light_effect = parametrize_combine([light_effect_smart, lightstrip])
|
||||
|
||||
|
||||
@led
|
||||
async def test_led_module(dev: Device, mocker: MockerFixture):
|
||||
"""Test fan speed feature."""
|
||||
led_module = dev.modules.get(Module.Led)
|
||||
assert led_module
|
||||
feat = led_module._module_features["led"]
|
||||
|
||||
call = mocker.spy(led_module, "call")
|
||||
await led_module.set_led(True)
|
||||
assert call.call_count == 1
|
||||
await dev.update()
|
||||
assert led_module.led is True
|
||||
assert feat.value is True
|
||||
|
||||
await led_module.set_led(False)
|
||||
assert call.call_count == 2
|
||||
await dev.update()
|
||||
assert led_module.led is False
|
||||
assert feat.value is False
|
||||
|
||||
await feat.set_value(True)
|
||||
assert call.call_count == 3
|
||||
await dev.update()
|
||||
assert feat.value is True
|
||||
assert led_module.led is True
|
||||
|
||||
|
||||
@light_effect
|
||||
async def test_light_effect_module(dev: Device, mocker: MockerFixture):
|
||||
"""Test fan speed feature."""
|
||||
light_effect_module = dev.modules[Module.LightEffect]
|
||||
assert light_effect_module
|
||||
feat = light_effect_module._module_features["light_effect"]
|
||||
|
||||
call = mocker.spy(light_effect_module, "call")
|
||||
effect_list = light_effect_module.effect_list
|
||||
assert "Off" in effect_list
|
||||
assert effect_list.index("Off") == 0
|
||||
assert len(effect_list) > 1
|
||||
assert effect_list == feat.choices
|
||||
|
||||
assert light_effect_module.has_custom_effects is not None
|
||||
|
||||
await light_effect_module.set_effect("Off")
|
||||
assert call.call_count == 1
|
||||
await dev.update()
|
||||
assert light_effect_module.effect == "Off"
|
||||
assert feat.value == "Off"
|
||||
|
||||
second_effect = effect_list[1]
|
||||
await light_effect_module.set_effect(second_effect)
|
||||
assert call.call_count == 2
|
||||
await dev.update()
|
||||
assert light_effect_module.effect == second_effect
|
||||
assert feat.value == second_effect
|
||||
|
||||
last_effect = effect_list[len(effect_list) - 1]
|
||||
await light_effect_module.set_effect(last_effect)
|
||||
assert call.call_count == 3
|
||||
await dev.update()
|
||||
assert light_effect_module.effect == last_effect
|
||||
assert feat.value == last_effect
|
||||
|
||||
# Test feature set
|
||||
await feat.set_value(second_effect)
|
||||
assert call.call_count == 4
|
||||
await dev.update()
|
||||
assert light_effect_module.effect == second_effect
|
||||
assert feat.value == second_effect
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await light_effect_module.set_effect("foobar")
|
||||
assert call.call_count == 4
|
@@ -16,7 +16,7 @@ from voluptuous import (
|
||||
Schema,
|
||||
)
|
||||
|
||||
from kasa import KasaException
|
||||
from kasa import KasaException, Module
|
||||
from kasa.iot import IotDevice
|
||||
|
||||
from .conftest import get_device_for_fixture_protocol, handle_turn_on, turn_on
|
||||
@@ -261,27 +261,26 @@ async def test_modules_not_supported(dev: IotDevice):
|
||||
|
||||
|
||||
async def test_get_modules():
|
||||
"""Test get_modules for child and parent modules."""
|
||||
"""Test getting modules for child and parent modules."""
|
||||
dummy_device = await get_device_for_fixture_protocol(
|
||||
"HS100(US)_2.0_1.5.6.json", "IOT"
|
||||
)
|
||||
from kasa.iot.modules import Cloud
|
||||
from kasa.smart.modules import CloudModule
|
||||
|
||||
# Modules on device
|
||||
module = dummy_device.get_module("Cloud")
|
||||
module = dummy_device.modules.get("cloud")
|
||||
assert module
|
||||
assert module._device == dummy_device
|
||||
assert isinstance(module, Cloud)
|
||||
|
||||
module = dummy_device.get_module(Cloud)
|
||||
module = dummy_device.modules.get(Module.IotCloud)
|
||||
assert module
|
||||
assert module._device == dummy_device
|
||||
assert isinstance(module, Cloud)
|
||||
|
||||
# Invalid modules
|
||||
module = dummy_device.get_module("DummyModule")
|
||||
module = dummy_device.modules.get("DummyModule")
|
||||
assert module is None
|
||||
|
||||
module = dummy_device.get_module(CloudModule)
|
||||
module = dummy_device.modules.get(Module.Cloud)
|
||||
assert module is None
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from kasa import DeviceType
|
||||
from kasa.exceptions import KasaException
|
||||
from kasa.iot import IotLightStrip
|
||||
|
||||
from .conftest import lightstrip
|
||||
@@ -23,7 +22,7 @@ async def test_lightstrip_effect(dev: IotLightStrip):
|
||||
|
||||
@lightstrip
|
||||
async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
|
||||
with pytest.raises(KasaException):
|
||||
with pytest.raises(ValueError):
|
||||
await dev.set_effect("Not real")
|
||||
|
||||
await dev.set_effect("Candy Cane")
|
||||
|
@@ -9,7 +9,7 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import KasaException
|
||||
from kasa import KasaException, Module
|
||||
from kasa.exceptions import SmartErrorCode
|
||||
from kasa.smart import SmartDevice
|
||||
|
||||
@@ -123,40 +123,39 @@ async def test_update_module_queries(dev: SmartDevice, mocker: MockerFixture):
|
||||
|
||||
|
||||
async def test_get_modules():
|
||||
"""Test get_modules for child and parent modules."""
|
||||
"""Test getting modules for child and parent modules."""
|
||||
dummy_device = await get_device_for_fixture_protocol(
|
||||
"KS240(US)_1.0_1.0.5.json", "SMART"
|
||||
)
|
||||
from kasa.iot.modules import AmbientLight
|
||||
from kasa.smart.modules import CloudModule, FanModule
|
||||
from kasa.smart.modules import CloudModule
|
||||
|
||||
# Modules on device
|
||||
module = dummy_device.get_module("CloudModule")
|
||||
module = dummy_device.modules.get("CloudModule")
|
||||
assert module
|
||||
assert module._device == dummy_device
|
||||
assert isinstance(module, CloudModule)
|
||||
|
||||
module = dummy_device.get_module(CloudModule)
|
||||
module = dummy_device.modules.get(Module.Cloud)
|
||||
assert module
|
||||
assert module._device == dummy_device
|
||||
assert isinstance(module, CloudModule)
|
||||
|
||||
# Modules on child
|
||||
module = dummy_device.get_module("FanModule")
|
||||
module = dummy_device.modules.get("FanModule")
|
||||
assert module
|
||||
assert module._device != dummy_device
|
||||
assert module._device._parent == dummy_device
|
||||
|
||||
module = dummy_device.get_module(FanModule)
|
||||
module = dummy_device.modules.get(Module.Fan)
|
||||
assert module
|
||||
assert module._device != dummy_device
|
||||
assert module._device._parent == dummy_device
|
||||
|
||||
# Invalid modules
|
||||
module = dummy_device.get_module("DummyModule")
|
||||
module = dummy_device.modules.get("DummyModule")
|
||||
assert module is None
|
||||
|
||||
module = dummy_device.get_module(AmbientLight)
|
||||
module = dummy_device.modules.get(Module.IotAmbientLight)
|
||||
assert module is None
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user