8000 Fix warnings in our test suite by rytilahti · Pull Request #1246 · python-kasa/python-kasa · GitHub
[go: up one dir, main page]

Skip to content

Fix warnings in our test suite #1246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions kasa/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,11 +484,11 @@ def __repr__(self) -> str:

_deprecated_device_type_attributes = {
# is_type
"is_bulb": (Module.Light, DeviceType.Bulb),
"is_dimmer": (Module.Light, DeviceType.Dimmer),
"is_light_strip": (Module.LightEffect, DeviceType.LightStrip),
"is_plug": (Module.Led, DeviceType.Plug),
"is_wallswitch": (Module.Led, DeviceType.WallSwitch),
"is_bulb": (None, DeviceType.Bulb),
"is_dimmer": (None, DeviceType.Dimmer),
"is_light_strip": (None, DeviceType.LightStrip),
"is_plug": (None, DeviceType.Plug),
"is_wallswitch": (None, DeviceType.WallSwitch),
"is_strip": (None, DeviceType.Strip),
"is_strip_socket": (None, DeviceType.StripSocket),
}
Expand All @@ -503,7 +503,9 @@ def _get_replacing_attr(
return None

for attr in attrs:
if hasattr(check, attr):
# Use dir() as opposed to hasattr() to avoid raising exceptions
# from properties
if attr in dir(check):
return attr

return None
Expand Down Expand Up @@ -552,10 +554,7 @@ def _get_replacing_attr(
def __getattr__(self, name: str) -> Any:
# is_device_type
if dep_device_type_attr := self._deprecated_device_type_attributes.get(name):
module = dep_device_type_attr[0]
msg = f"{name} is deprecated"
if module:
msg += f", use: {module} in device.modules instead"
msg = f"{name} is deprecated, use device_type property instead"
warn(msg, DeprecationWarning, stacklevel=2)
return self.device_type == dep_device_type_attr[1]
# Other deprecated attributes
Expand Down
13 changes: 6 additions & 7 deletions tests/fakeprotocol_smart.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def __init__(
warn_fixture_missing_methods=True,
fix_incomplete_fixture_lists=True,
is_child=False,
get_child_fixtures=True,
):
super().__init__(
config=DeviceConfig(
Expand All @@ -48,9 +49,10 @@ def __init__(
# child are then still reflected on the parent's lis of child device in
if not is_child:
self.info = copy.deepcopy(info)
self.child_protocols = self._get_child_protocols(
self.info, self.fixture_name, "get_child_device_list"
)
if get_child_fixtures:
self.child_protocols = self._get_child_protocols(
self.info, self.fixture_name, "get_child_device_list"
)
else:
self.info = info
if not component_nego_not_included:
Expand Down Expand Up @@ -220,10 +222,7 @@ async def _handle_control_child(self, params: dict):
"""Handle control_child command."""
device_id = params.get("device_id")
if device_id not in self.child_protocols:
warn(
f"Could not find child fixture {device_id} in {self.fixture_name}",
stacklevel=2,
)
# no need to warn as the warning was raised during protocol init
return self._handle_control_child_missing(params)

child_protocol: SmartProtocol = self.child_protocols[device_id]
Expand Down
4 changes: 2 additions & 2 deletions tests/smart/modules/test_contact.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from kasa import Module, SmartDevice
from kasa import Device, Module

from ...device_fixtures import parametrize

Expand All @@ -16,7 +16,7 @@
("is_open", bool),
],
)
async def test_contact_features(dev: SmartDevice, feature, type):
async def test_contact_features(dev: Device, feature, type):
"""Test that features are registered and work as expected."""
contact = dev.modules.get(Module.ContactSensor)
assert contact is not None
Expand Down
2 changes: 1 addition & 1 deletion tests/smart/modules/test_light_effect.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async def test_light_effect_brightness(

if effect_active:
assert light_effect.is_active
assert light_effect.brightness == dev.brightness
assert light_effect.brightness == light_module.brightness

light_effect_set_brightness.assert_called_with(10)
mock_light_effect_call.assert_called_with(
Expand Down
2 changes: 1 addition & 1 deletion tests/smart/modules/test_light_strip_effect.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async def test_light_effect_brightness(

if effect_active:
assert light_effect.is_active
assert light_effect.brightness == dev.brightness
assert light_effect.brightness == light_module.brightness

light_effect_set_brightness.assert_called_with(10)
mock_light_effect_call.assert_called_with(
Expand Down
4 changes: 2 additions & 2 deletions tests/smart/modules/test_motionsensor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from kasa import Module, SmartDevice
from kasa import Device, Module

from ...device_fixtures import parametrize

Expand All @@ -16,7 +16,7 @@
("motion_detected", bool),
],
)
async def test_motion_features(dev: SmartDevice, feature, type):
async def test_motion_features(dev: Device, feature, type):
"""Test that features are registered and work as expected."""
motion = dev.modules.get(Module.MotionSensor)
assert motion is not None
Expand Down
77 changes: 46 additions & 31 deletions tests/test_bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from kasa import Device, DeviceType, IotLightPreset, KasaException, LightState, Module
from kasa.iot import IotBulb, IotDimmer
from kasa.iot.modules import LightPreset as IotLightPresetModule

from .conftest import (
bulb,
Expand All @@ -39,11 +40,6 @@ async def test_bulb_sysinfo(dev: Device):

assert dev.model is not None

# TODO: remove special handling for lightstrip
if not dev.is_light_strip:
assert dev.device_type == DeviceType.Bulb
assert dev.is_bulb


@bulb
async def test_state_attributes(dev: Device):
Expand Down Expand Up @@ -88,7 +84,9 @@ async def test_hsv(dev: Device, turn_on):
@color_bulb_iot
async def test_set_hsv_transition(dev: IotBulb, mocker):
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
await dev.set_hsv(10, 10, 100, transition=1000)
light = dev.modules.get(Module.Light)
assert light
await light.set_hsv(10, 10, 100, transition=1000)

set_light_state.assert_called_with(
{"hue": 10, "saturation": 10, "brightness": 100, "color_temp": 0},
Expand Down Expand Up @@ -226,16 +224,19 @@ async def test_try_set_colortemp(dev: Device, turn_on):
@variable_temp_iot
async def test_set_color_temp_transition(dev: IotBulb, mocker):
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
await dev.set_color_temp(2700, transition=100)
light = dev.modules.get(Module.Light)
assert light
await light.set_color_temp(2700, transition=100)

set_light_state.assert_called_with({"color_temp": 2700}, transition=100)


@variable_temp_iot
async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")

assert dev.valid_temperature_range == (2700, 5000)
light = dev.modules.get(Module.Light)
assert light
assert light.valid_temperature_range == (2700, 5000)
assert "Unknown color temperature range, fallback to 2700-5000" in caplog.text


Expand Down Expand Up @@ -278,19 +279,21 @@ async def test_non_variable_temp(dev: Device):
@turn_on
async def test_dimmable_brightness(dev: IotBulb, turn_on):
assert isinstance(dev, (IotBulb, IotDimmer))
light = dev.modules.get(Module.Light)
assert light
await handle_turn_on(dev, turn_on)
assert dev._is_dimmable

await dev.set_brightness(50)
await light.set_brightness(50)
await dev.update()
assert dev.brightness == 50
assert light.brightness == 50

await dev.set_brightness(10)
await light.set_brightness(10)
await dev.update()
assert dev.brightness == 10
assert light.brightness == 10

with pytest.raises(TypeError, match="Brightness must be an integer"):
await dev.set_brightness("foo") # type: ignore[arg-type]
await light.set_brightness("foo") # type: ignore[arg-type]


@bulb_iot
Expand All @@ -308,36 +311,40 @@ async def test_turn_on_transition(dev: IotBulb, mocker):
@bulb_iot
async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
set_light_state = mocker.patch("kasa.iot.IotBulb._set_light_state")
await dev.set_brightness(10, transition=1000)
light = dev.modules.get(Module.Light)
assert light
await light.set_brightness(10, transition=1000)

set_light_state.assert_called_with({"brightness": 10, "on_off": 1}, transition=1000)


@dimmable_iot
async def test_invalid_brightness(dev: IotBulb):
assert dev._is_dimmable

light = dev.modules.get(Module.Light)
assert light
with pytest.raises(
ValueError,
match=re.escape("Invalid brightness value: 110 (valid range: 0-100%)"),
):
await dev.set_brightness(110)
await light.set_brightness(110)

with pytest.raises(
ValueError,
match=re.escape("Invalid brightness value: -100 (valid range: 0-100%)"),
):
await dev.set_brightness(-100)
await light.set_brightness(-100)


@non_dimmable_iot
async def test_non_dimmable(dev: IotBulb):
assert not dev._is_dimmable

light = dev.modules.get(Module.Light)
assert light
with pytest.raises(KasaException):
assert dev.brightness == 0
assert light.brightness == 0
with pytest.raises(KasaException):
await dev.set_brightness(100)
await light.set_brightness(100)


@bulb_iot
Expand All @@ -357,7 +364,10 @@ async def test_ignore_default_not_set_without_color_mode_change_turn_on(

@bulb_iot
async def test_list_presets(dev: IotBulb):
presets = dev.presets
light_preset = dev.modules.get(Module.LightPreset)
assert light_preset
assert isinstance(light_preset, IotLightPresetModule)
presets = light_preset._deprecated_presets
# Light strip devices may list some light effects along with normal presets but these
# are handled by the LightEffect module so exclude preferred states with id
raw_presets = [
Expand All @@ -376,9 +386,13 @@ async def test_list_presets(dev: IotBulb):
@bulb_iot
async def test_modify_preset(dev: IotBulb, mocker):
"""Verify that modifying preset calls the and exceptions are raised properly."""
if not dev.presets:
if (
not (light_preset := dev.modules.get(Module.LightPreset))
or not light_preset._deprecated_presets
):
pytest.skip("Some strips do not support presets")

assert isinstance(light_preset, IotLightPresetModule)
data: dict[str, int | None] = {
"index": 0,
"brightness": 10,
Expand All @@ -394,12 +408,12 @@ async def test_modify_preset(dev: IotBulb, mocker):
assert preset.saturation == 0
assert preset.color_temp == 0

await dev.save_preset(preset)
await light_preset._deprecated_save_preset(preset)
await dev.update()
assert dev.presets[0].brightness == 10
assert light_preset._deprecated_presets[0].brightness == 10

with pytest.raises(KasaException):
await dev.save_preset(
await light_preset._deprecated_save_preset(
IotLightPreset(index=5, hue=0, brightness=0, saturation=0, color_temp=0) # type: ignore[call-arg]
)

Expand All @@ -420,11 +434,14 @@ async def test_modify_preset(dev: IotBulb, mocker):
)
async def test_modify_preset_payloads(dev: IotBulb, preset, payload, mocker):
"""Test that modify preset payloads ignore none values."""
if not dev.presets:
if (
not (light_preset := dev.modules.get(Module.LightPreset))
or not light_preset._deprecated_presets
):
pytest.skip("Some strips do not support presets")

query_helper = mocker.patch("kasa.iot.IotBulb._query_helper")
await dev.save_preset(preset)
await light_preset._deprecated_save_preset(preset)
query_helper.assert_called_with(dev.LIGHT_SERVICE, "set_preferred_state", payload)


Expand Down Expand Up @@ -476,6 +493,4 @@ async def test_modify_preset_payloads(dev: IotBulb, preset, payload, mocker):

@bulb
def test_device_type_bulb(dev: Device):
if dev.is_light_strip:
pytest.skip("bulb has also lightstrips to test the api")
assert dev.device_type == DeviceType.Bulb
assert dev.device_type in {DeviceType.Bulb, DeviceType.LightStrip}
26 changes: 18 additions & 8 deletions tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import inspect
import pkgutil
import sys
from contextlib import AbstractContextManager
from contextlib import AbstractContextManager, nullcontext
from unittest.mock import AsyncMock, patch

import pytest
Expand Down Expand Up @@ -170,15 +170,22 @@ async def _test_attribute(
dev: Device, attribute_name, is_expected, module_name, *args, will_raise=False
):
if is_expected and will_raise:
ctx: AbstractContextManager = pytest.raises(will_raise)
ctx: AbstractContextManager | nullcontext = pytest.raises(will_raise)
dep_context: pytest.WarningsRecorder | nullcontext = pytest.deprecated_call(
match=(f"{attribute_name} is deprecated, use:")
)
elif is_expected:
ctx = pytest.deprecated_call(match=(f"{attribute_name} is deprecated, use:"))
ctx = nullcontext()
dep_context = pytest.deprecated_call(
match=(f"{attribute_name} is deprecated, use:")
)
else:
ctx = pytest.raises(
AttributeError, match=f"Device has no attribute '{attribute_name}'"
)
dep_context = nullcontext()

with ctx:
with dep_context, ctx:
if args:
await getattr(dev, attribute_name)(*args)
else:
Expand Down Expand Up @@ -267,16 +274,19 @@ async def test_deprecated_light_preset_attributes(dev: Device):
await _test_attribute(dev, "presets", bool(preset), "LightPreset", will_raise=exc)

exc = None
is_expected = bool(preset)
# deprecated save_preset not implemented for smart devices as it's unlikely anyone
# has an existing reliance on this for the newer devices.
if not preset or isinstance(dev, SmartDevice):
exc = AttributeError
elif len(preset.preset_states_list) == 0:
if isinstance(dev, SmartDevice):
is_expected = False

if preset and len(preset.preset_states_list) == 0:
exc = KasaException

await _test_attribute(
dev,
"save_preset",
bool(preset),
is_expected,
"LightPreset",
IotLightPreset(index=0, hue=100, brightness=100, saturation=0, color_temp=0), # type: ignore[call-arg]
will_raise=exc,
Expand Down
Loading
Loading
0