Migrate Light module to feature based capability checks

This commit is contained in:
Steven B
2024-11-22 17:02:38 +00:00
parent c5830a4cdc
commit 8bfddbdd71
8 changed files with 167 additions and 155 deletions

View File

@@ -25,11 +25,11 @@ Get the light module to interact:
You can use the ``is_``-prefixed properties to check for supported features:
>>> light.is_dimmable
>>> light.has_feature(light.set_brightness)
True
>>> light.is_color
>>> light.has_feature(light.set_hsv)
True
>>> light.is_variable_color_temp
>>> light.has_feature(light.set_color_temp)
True
All known bulbs support changing the brightness:
@@ -43,8 +43,9 @@ All known bulbs support changing the brightness:
Bulbs supporting color temperature can be queried for the supported range:
>>> light.valid_temperature_range
ColorTempRange(min=2500, max=6500)
>>> if color_temp_feature := light.get_feature(light.set_color_temp):
>>> print(f"{color_temp_feature.minimum_value}, {color_temp_feature.maximum_value}")
2500, 6500
>>> await light.set_color_temp(3000)
>>> await dev.update()
>>> light.color_temp
@@ -63,10 +64,13 @@ HSV(hue=180, saturation=100, value=80)
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from typing import NamedTuple
from typing import Annotated, Any, NamedTuple
from warnings import warn
from ..module import Module
from ..exceptions import KasaException
from ..module import FeatureAttribute, Module
@dataclass
@@ -101,35 +105,7 @@ class Light(Module, ABC):
@property
@abstractmethod
def is_dimmable(self) -> bool:
"""Whether the light supports brightness changes."""
@property
@abstractmethod
def is_color(self) -> bool:
"""Whether the bulb supports color changes."""
@property
@abstractmethod
def is_variable_color_temp(self) -> bool:
"""Whether the bulb supports color temperature changes."""
@property
@abstractmethod
def valid_temperature_range(self) -> ColorTempRange:
"""Return the device-specific white temperature range (in Kelvin).
:return: White temperature range in Kelvin (minimum, maximum)
"""
@property
@abstractmethod
def has_effects(self) -> bool:
"""Return True if the device supports effects."""
@property
@abstractmethod
def hsv(self) -> HSV:
def hsv(self) -> Annotated[HSV, FeatureAttribute()]:
"""Return the current HSV state of the bulb.
:return: hue, saturation and value (degrees, %, %)
@@ -137,12 +113,12 @@ class Light(Module, ABC):
@property
@abstractmethod
def color_temp(self) -> int:
def color_temp(self) -> Annotated[int, FeatureAttribute()]:
"""Whether the bulb supports color temperature changes."""
@property
@abstractmethod
def brightness(self) -> int:
def brightness(self) -> Annotated[int, FeatureAttribute()]:
"""Return the current brightness in percentage."""
@abstractmethod
@@ -153,7 +129,7 @@ class Light(Module, ABC):
value: int | None = None,
*,
transition: int | None = None,
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set new HSV.
Note, transition is not supported and will be ignored.
@@ -167,7 +143,7 @@ class Light(Module, ABC):
@abstractmethod
async def set_color_temp(
self, temp: int, *, brightness: int | None = None, transition: int | None = None
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set the color temperature of the device in kelvin.
Note, transition is not supported and will be ignored.
@@ -179,7 +155,7 @@ class Light(Module, ABC):
@abstractmethod
async def set_brightness(
self, brightness: int, *, transition: int | None = None
) -> dict:
) -> Annotated[dict, FeatureAttribute()]:
"""Set the brightness in percentage.
Note, transition is not supported and will be ignored.
@@ -196,3 +172,42 @@ class Light(Module, ABC):
@abstractmethod
async def set_state(self, state: LightState) -> dict:
"""Set the light state."""
def _deprecated_valid_temperature_range(self) -> ColorTempRange:
if not (temp := self.get_feature(self.set_color_temp)):
raise KasaException("Color temperature not supported")
return ColorTempRange(temp.minimum_value, temp.maximum_value)
def _deprecated_attributes(self, dep_name: str) -> Callable | None:
map: dict[str, Callable] = {
"is_color": self.set_hsv,
"is_dimmable": self.set_brightness,
"is_variable_color_temp": self.set_color_temp,
}
return map.get(dep_name)
def __getattr__(self, name: str) -> Any:
if name == "valid_temperature_range":
res = self._deprecated_valid_temperature_range()
msg = (
"valid_temperature_range is deprecated, use "
"get_feature(self.set_color_temp) minimum_value "
" and maximum_value instead"
)
warn(msg, DeprecationWarning, stacklevel=2)
return res
if name == "has_effects":
msg = (
"has_effects is deprecated, use Module.LightEffect "
"in device.modules instead"
)
warn(msg, DeprecationWarning, stacklevel=2)
return Module.LightEffect in self._device.modules
if attr := self._deprecated_attributes(name):
msg = f"{name} is deprecated, use has_feature({attr}) instead"
warn(msg, DeprecationWarning, stacklevel=2)
return self.has_feature(attr)
raise AttributeError(f"Energy module has no attribute {name!r}")

View File

@@ -13,8 +13,7 @@ Living Room Bulb
Light effects are accessed via the LightPreset module. To list available presets
>>> if dev.modules[Module.Light].has_effects:
>>> light_effect = dev.modules[Module.LightEffect]
>>> light_effect = dev.modules[Module.LightEffect]
>>> light_effect.effect_list
['Off', 'Party', 'Relax']