From 41e341028ea88803e4f0f00c77d8022db9473b50 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 16 Nov 2021 12:39:51 +0100 Subject: [PATCH] Add typing to deCONZ Fan and Light platforms (#59607) --- homeassistant/components/deconz/fan.py | 43 ++++++----- homeassistant/components/deconz/light.py | 95 ++++++++++++++---------- 2 files changed, 81 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 40862bfcde1..d1ff85f9d65 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -1,6 +1,9 @@ """Support for deCONZ fans.""" from __future__ import annotations +from collections.abc import ValuesView +from typing import Any + from pydeconz.light import ( FAN_SPEED_25_PERCENT, FAN_SPEED_50_PERCENT, @@ -19,15 +22,17 @@ from homeassistant.components.fan import ( SUPPORT_SET_SPEED, FanEntity, ) -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.percentage import ( ordered_list_item_to_percentage, percentage_to_ordered_list_item, ) from .deconz_device import DeconzDevice -from .gateway import get_gateway_from_config_entry +from .gateway import DeconzGateway, get_gateway_from_config_entry ORDERED_NAMED_FAN_SPEEDS = [ FAN_SPEED_25_PERCENT, @@ -50,13 +55,19 @@ LEGACY_DECONZ_TO_SPEED = { } -async def async_setup_entry(hass, config_entry, async_add_entities) -> None: +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up fans for deCONZ component.""" gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() @callback - def async_add_fan(lights=gateway.api.lights.values()) -> None: + def async_add_fan( + lights: list[Fan] | ValuesView[Fan] = gateway.api.lights.values(), + ) -> None: """Add fan from deCONZ.""" entities = [] @@ -86,8 +97,11 @@ class DeconzFan(DeconzDevice, FanEntity): """Representation of a deCONZ fan.""" TYPE = DOMAIN + _device: Fan - def __init__(self, device, gateway) -> None: + _attr_supported_features = SUPPORT_SET_SPEED + + def __init__(self, device: Fan, gateway: DeconzGateway) -> None: """Set up fan.""" super().__init__(device, gateway) @@ -95,12 +109,10 @@ class DeconzFan(DeconzDevice, FanEntity): if self._device.speed in ORDERED_NAMED_FAN_SPEEDS: self._default_on_speed = self._device.speed - self._attr_supported_features = SUPPORT_SET_SPEED - @property def is_on(self) -> bool: """Return true if fan is on.""" - return self._device.speed != FAN_SPEED_OFF + return self._device.speed != FAN_SPEED_OFF # type: ignore[no-any-return] @property def percentage(self) -> int | None: @@ -153,11 +165,6 @@ class DeconzFan(DeconzDevice, FanEntity): SPEED_MEDIUM, ) - @property - def supported_features(self) -> int: - """Flag supported features.""" - return self._attr_supported_features - @callback def async_update_callback(self) -> None: """Store latest configured speed from the device.""" @@ -183,10 +190,10 @@ class DeconzFan(DeconzDevice, FanEntity): async def async_turn_on( self, - speed: str = None, - percentage: int = None, - preset_mode: str = None, - **kwargs, + speed: str | None = None, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, ) -> None: """Turn on fan.""" new_speed = self._default_on_speed @@ -198,6 +205,6 @@ class DeconzFan(DeconzDevice, FanEntity): await self._device.set_speed(new_speed) - async def async_turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Turn off fan.""" await self._device.set_speed(FAN_SPEED_OFF) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 6bb4f5c5b00..e287d574633 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -2,6 +2,9 @@ from __future__ import annotations +from collections.abc import ValuesView +from typing import Any + from pydeconz.group import DeconzGroup as Group from pydeconz.light import ( ALERT_LONG, @@ -33,27 +36,35 @@ from homeassistant.components.light import ( SUPPORT_TRANSITION, LightEntity, ) -from homeassistant.core import callback +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.color import color_hs_to_xy from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS from .deconz_device import DeconzDevice -from .gateway import get_gateway_from_config_entry +from .gateway import DeconzGateway, get_gateway_from_config_entry 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} -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the deCONZ lights and groups from a config entry.""" gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() @callback - def async_add_light(lights=gateway.api.lights.values()): + def async_add_light( + lights: list[Light] | ValuesView[Light] = gateway.api.lights.values(), + ) -> None: """Add light from deCONZ.""" entities = [] @@ -77,7 +88,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) @callback - def async_add_group(groups=gateway.api.groups.values()): + def async_add_group( + groups: list[Group] | ValuesView[Group] = gateway.api.groups.values(), + ) -> None: """Add group from deCONZ.""" if not gateway.option_allow_deconz_groups: return @@ -113,11 +126,11 @@ class DeconzBaseLight(DeconzDevice, LightEntity): TYPE = DOMAIN - def __init__(self, device, gateway): + def __init__(self, device: Group | Light, gateway: DeconzGateway) -> None: """Set up light.""" super().__init__(device, gateway) - self._attr_supported_color_modes = set() + self._attr_supported_color_modes: set[str] = set() if device.color_temp is not None: self._attr_supported_color_modes.add(COLOR_MODE_COLOR_TEMP) @@ -158,83 +171,83 @@ class DeconzBaseLight(DeconzDevice, LightEntity): return color_mode @property - def brightness(self): + def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" - return self._device.brightness + return self._device.brightness # type: ignore[no-any-return] @property - def color_temp(self): + def color_temp(self) -> int: """Return the CT color value.""" - return self._device.color_temp + return self._device.color_temp # type: ignore[no-any-return] @property - def hs_color(self) -> tuple: + def hs_color(self) -> tuple[float, float]: """Return the hs color value.""" return (self._device.hue / 65535 * 360, self._device.saturation / 255 * 100) @property - def xy_color(self) -> tuple | None: + def xy_color(self) -> tuple[float, float] | None: """Return the XY color value.""" - return self._device.xy + return self._device.xy # type: ignore[no-any-return] @property - def is_on(self): + def is_on(self) -> bool: """Return true if light is on.""" - return self._device.state + return self._device.state # type: ignore[no-any-return] - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn on light.""" - data = {"on": True} + data: dict[str, bool | float | int | str | tuple[float, float]] = {"on": True} - if ATTR_BRIGHTNESS in kwargs: - data["brightness"] = kwargs[ATTR_BRIGHTNESS] + if attr_brightness := kwargs.get(ATTR_BRIGHTNESS): + data["brightness"] = attr_brightness - if ATTR_COLOR_TEMP in kwargs: - data["color_temperature"] = kwargs[ATTR_COLOR_TEMP] + if attr_color_temp := kwargs.get(ATTR_COLOR_TEMP): + data["color_temperature"] = attr_color_temp - if ATTR_HS_COLOR in kwargs: + if attr_hs_color := kwargs.get(ATTR_HS_COLOR): if COLOR_MODE_XY in self._attr_supported_color_modes: - data["xy"] = color_hs_to_xy(*kwargs[ATTR_HS_COLOR]) + data["xy"] = color_hs_to_xy(*attr_hs_color) else: - data["hue"] = int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535) - data["saturation"] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255) + data["hue"] = int(attr_hs_color[0] / 360 * 65535) + data["saturation"] = int(attr_hs_color[1] / 100 * 255) if ATTR_XY_COLOR in kwargs: data["xy"] = kwargs[ATTR_XY_COLOR] - if ATTR_TRANSITION in kwargs: - data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10) + if attr_transition := kwargs.get(ATTR_TRANSITION): + data["transition_time"] = int(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: + if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH, ""))) is not None: data["alert"] = alert del data["on"] - if (effect := EFFECT_TO_DECONZ.get(kwargs.get(ATTR_EFFECT))) is not None: + if (effect := EFFECT_TO_DECONZ.get(kwargs.get(ATTR_EFFECT, ""))) is not None: data["effect"] = effect await self._device.set_state(**data) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn off light.""" if not self._device.state: return - data = {"on": False} + data: dict[str, bool | int | str] = {"on": False} if ATTR_TRANSITION in kwargs: data["brightness"] = 0 data["transition_time"] = int(kwargs[ATTR_TRANSITION] * 10) - if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH))) is not None: + if (alert := FLASH_TO_DECONZ.get(kwargs.get(ATTR_FLASH, ""))) is not None: data["alert"] = alert del data["on"] await self._device.set_state(**data) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, bool]: """Return the device state attributes.""" return {DECONZ_GROUP: isinstance(self._device, Group)} @@ -242,13 +255,15 @@ class DeconzBaseLight(DeconzDevice, LightEntity): class DeconzLight(DeconzBaseLight): """Representation of a deCONZ light.""" + _device: Light + @property - def max_mireds(self): + def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" return self._device.max_color_temp or super().max_mireds @property - def min_mireds(self): + def min_mireds(self) -> int: """Return the coldest color_temp that this light supports.""" return self._device.min_color_temp or super().min_mireds @@ -256,13 +271,15 @@ class DeconzLight(DeconzBaseLight): class DeconzGroup(DeconzBaseLight): """Representation of a deCONZ group.""" - def __init__(self, device, gateway): + _device: Group + + def __init__(self, device: Group, gateway: DeconzGateway) -> None: """Set up group and create an unique id.""" self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}" super().__init__(device, gateway) @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique identifier for this device.""" return self._unique_id @@ -278,7 +295,7 @@ class DeconzGroup(DeconzBaseLight): ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, bool]: """Return the device state attributes.""" attributes = dict(super().extra_state_attributes) attributes["all_on"] = self._device.all_on