diff --git a/.strict-typing b/.strict-typing index 5a91ed7ef51c..fb71591ccafc 100644 --- a/.strict-typing +++ b/.strict-typing @@ -68,6 +68,7 @@ homeassistant.components.deconz.climate homeassistant.components.deconz.config_flow homeassistant.components.deconz.diagnostics homeassistant.components.deconz.gateway +homeassistant.components.deconz.light homeassistant.components.deconz.services homeassistant.components.device_automation.* homeassistant.components.device_tracker.* diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 98761324df43..2335914cbd45 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -1,7 +1,7 @@ """Support for deCONZ lights.""" from __future__ import annotations -from typing import Any +from typing import Any, Generic, TypedDict, TypeVar from pydeconz.group import Group from pydeconz.light import ( @@ -10,6 +10,7 @@ from pydeconz.light import ( EFFECT_COLOR_LOOP, EFFECT_NONE, Light, + LightResources, ) from homeassistant.components.light import ( @@ -47,6 +48,22 @@ DECONZ_GROUP = "is_deconz_group" EFFECT_TO_DECONZ = {EFFECT_COLORLOOP: EFFECT_COLOR_LOOP, "None": EFFECT_NONE} FLASH_TO_DECONZ = {FLASH_SHORT: ALERT_SHORT, FLASH_LONG: ALERT_LONG} +_L = TypeVar("_L", Group, Light) + + +class SetStateAttributes(TypedDict, total=False): + """Attributes available with set state call.""" + + alert: str + brightness: int + color_temperature: int + effect: str + hue: int + on: bool + saturation: int + transition_time: int + xy: tuple[float, float] + async def async_setup_entry( hass: HomeAssistant, @@ -58,7 +75,7 @@ async def async_setup_entry( gateway.entities[DOMAIN] = set() @callback - def async_add_light(lights: list[Light] | None = None) -> None: + def async_add_light(lights: list[LightResources] | None = None) -> None: """Add light from deCONZ.""" entities = [] @@ -119,12 +136,14 @@ async def async_setup_entry( async_add_group() -class DeconzBaseLight(DeconzDevice, LightEntity): +class DeconzBaseLight(Generic[_L], DeconzDevice, LightEntity): """Representation of a deCONZ light.""" TYPE = DOMAIN - def __init__(self, device: Group | Light, gateway: DeconzGateway) -> None: + _device: _L + + def __init__(self, device: _L, gateway: DeconzGateway) -> None: """Set up light.""" super().__init__(device, gateway) @@ -154,7 +173,7 @@ class DeconzBaseLight(DeconzDevice, LightEntity): self._attr_effect_list = [EFFECT_COLORLOOP] @property - def color_mode(self) -> str: + def color_mode(self) -> str | None: """Return the color mode of the light.""" if self._device.color_mode == "ct": color_mode = COLOR_MODE_COLOR_TEMP @@ -174,14 +193,16 @@ class DeconzBaseLight(DeconzDevice, LightEntity): return self._device.brightness # type: ignore[no-any-return] @property - def color_temp(self) -> int: + def color_temp(self) -> int | None: """Return the CT color value.""" return self._device.color_temp # type: ignore[no-any-return] @property - def hs_color(self) -> tuple[float, float]: + def hs_color(self) -> tuple[float, float] | None: """Return the hs color value.""" - return (self._device.hue / 65535 * 360, self._device.saturation / 255 * 100) + if (hue := self._device.hue) and (sat := self._device.saturation): + return (hue / 65535 * 360, sat / 255 * 100) + return None @property def xy_color(self) -> tuple[float, float] | None: @@ -189,41 +210,41 @@ class DeconzBaseLight(DeconzDevice, LightEntity): return self._device.xy # type: ignore[no-any-return] @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if light is on.""" return self._device.state # type: ignore[no-any-return] async def async_turn_on(self, **kwargs: Any) -> None: """Turn on light.""" - data: dict[str, bool | float | int | str | tuple[float, float]] = {"on": True} + data: SetStateAttributes = {"on": True} - if (attr_brightness := kwargs.get(ATTR_BRIGHTNESS)) is not None: - data["brightness"] = attr_brightness + if ATTR_BRIGHTNESS in kwargs: + data["brightness"] = kwargs[ATTR_BRIGHTNESS] - if attr_color_temp := kwargs.get(ATTR_COLOR_TEMP): - data["color_temperature"] = attr_color_temp + if ATTR_COLOR_TEMP in kwargs: + data["color_temperature"] = kwargs[ATTR_COLOR_TEMP] - if attr_hs_color := kwargs.get(ATTR_HS_COLOR): + if ATTR_HS_COLOR in kwargs: if COLOR_MODE_XY in self._attr_supported_color_modes: - data["xy"] = color_hs_to_xy(*attr_hs_color) + data["xy"] = color_hs_to_xy(*kwargs[ATTR_HS_COLOR]) else: - data["hue"] = int(attr_hs_color[0] / 360 * 65535) - data["saturation"] = int(attr_hs_color[1] / 100 * 255) + data["hue"] = int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535) + data["saturation"] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255) if ATTR_XY_COLOR in kwargs: data["xy"] = kwargs[ATTR_XY_COLOR] - if (attr_transition := kwargs.get(ATTR_TRANSITION)) is not None: - data["transition_time"] = int(attr_transition * 10) + if ATTR_TRANSITION in kwargs: + data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10) elif "IKEA" in self._device.manufacturer: data["transition_time"] = 0 - if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH, ""))) is not None: - data["alert"] = alert + if ATTR_FLASH in kwargs and kwargs[ATTR_FLASH] in FLASH_TO_DECONZ: + data["alert"] = FLASH_TO_DECONZ[kwargs[ATTR_FLASH]] del data["on"] - if (effect := EFFECT_TO_DECONZ.get(kwargs.get(ATTR_EFFECT, ""))) is not None: - data["effect"] = effect + if ATTR_EFFECT in kwargs and kwargs[ATTR_EFFECT] in EFFECT_TO_DECONZ: + data["effect"] = EFFECT_TO_DECONZ[kwargs[ATTR_EFFECT]] await self._device.set_state(**data) @@ -232,14 +253,14 @@ class DeconzBaseLight(DeconzDevice, LightEntity): if not self._device.state: return - data: dict[str, bool | int | str] = {"on": False} + data: SetStateAttributes = {"on": False} - if (attr_transition := kwargs.get(ATTR_TRANSITION)) is not None: + if ATTR_TRANSITION in kwargs: data["brightness"] = 0 - data["transition_time"] = int(attr_transition * 10) + data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10) - if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH, ""))) is not None: - data["alert"] = alert + if ATTR_FLASH in kwargs and kwargs[ATTR_FLASH] in FLASH_TO_DECONZ: + data["alert"] = FLASH_TO_DECONZ[kwargs[ATTR_FLASH]] del data["on"] await self._device.set_state(**data) @@ -250,7 +271,7 @@ class DeconzBaseLight(DeconzDevice, LightEntity): return {DECONZ_GROUP: isinstance(self._device, Group)} -class DeconzLight(DeconzBaseLight): +class DeconzLight(DeconzBaseLight[Light]): """Representation of a deCONZ light.""" _device: Light @@ -266,7 +287,7 @@ class DeconzLight(DeconzBaseLight): return self._device.min_color_temp or super().min_mireds -class DeconzGroup(DeconzBaseLight): +class DeconzGroup(DeconzBaseLight[Group]): """Representation of a deCONZ group.""" _device: Group diff --git a/mypy.ini b/mypy.ini index 822e45149ae3..1bddbe4de9c9 100644 --- a/mypy.ini +++ b/mypy.ini @@ -550,6 +550,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.deconz.light] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.deconz.services] check_untyped_defs = true disallow_incomplete_defs = true @@ -2618,9 +2629,6 @@ ignore_errors = true [mypy-homeassistant.components.deconz.fan] ignore_errors = true -[mypy-homeassistant.components.deconz.light] -ignore_errors = true - [mypy-homeassistant.components.deconz.lock] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 8314a9ae3e0f..c43cf47cd2e1 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -26,7 +26,6 @@ IGNORED_MODULES: Final[list[str]] = [ "homeassistant.components.deconz.binary_sensor", "homeassistant.components.deconz.cover", "homeassistant.components.deconz.fan", - "homeassistant.components.deconz.light", "homeassistant.components.deconz.lock", "homeassistant.components.deconz.logbook", "homeassistant.components.deconz.number",