From deff0ad61e8ed01a9ffc2e5c4e224af1fb444649 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:18:29 +0200 Subject: [PATCH] Implement generic in Deconz base device (#76015) * Make DevonzBase a generic * Adjust alarm_control_panel * Adjust binary_sensor * Adjust climate * More platforms * Adjust light * Ignore type-var * Add space * Implement recommendation * Use type: ignore[union-attr] * Revert "Use type: ignore[union-attr]" This reverts commit 983443062aab0a9c599b2750d823d0c5148c05ce. * Adjust assert * Adjust lock * Rename type variables * type: ignore[union-attr] * Formatting Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .../components/deconz/alarm_control_panel.py | 3 +- .../components/deconz/binary_sensor.py | 3 +- homeassistant/components/deconz/climate.py | 3 +- homeassistant/components/deconz/cover.py | 3 +- .../components/deconz/deconz_device.py | 41 ++++++++++++------- homeassistant/components/deconz/fan.py | 3 +- homeassistant/components/deconz/light.py | 14 ++----- homeassistant/components/deconz/lock.py | 5 +-- homeassistant/components/deconz/number.py | 3 +- homeassistant/components/deconz/sensor.py | 3 +- homeassistant/components/deconz/siren.py | 3 +- homeassistant/components/deconz/switch.py | 3 +- 12 files changed, 41 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index bf0f39b75d0..e1fb0757b12 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -80,11 +80,10 @@ async def async_setup_entry( ) -class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): +class DeconzAlarmControlPanel(DeconzDevice[AncillaryControl], AlarmControlPanelEntity): """Representation of a deCONZ alarm control panel.""" TYPE = DOMAIN - _device: AncillaryControl _attr_code_format = CodeFormat.NUMBER _attr_supported_features = ( diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 814dec443e0..a7dbc2eacff 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -233,11 +233,10 @@ async def async_setup_entry( ) -class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): +class DeconzBinarySensor(DeconzDevice[SensorResources], BinarySensorEntity): """Representation of a deCONZ binary sensor.""" TYPE = DOMAIN - _device: SensorResources entity_description: DeconzBinarySensorDescription def __init__( diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 75b37db2c13..e49918e14f2 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -92,11 +92,10 @@ async def async_setup_entry( ) -class DeconzThermostat(DeconzDevice, ClimateEntity): +class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity): """Representation of a deCONZ thermostat.""" TYPE = DOMAIN - _device: Thermostat _attr_temperature_unit = TEMP_CELSIUS diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 3e56882f15a..8df974cf146 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -49,11 +49,10 @@ async def async_setup_entry( ) -class DeconzCover(DeconzDevice, CoverEntity): +class DeconzCover(DeconzDevice[Cover], CoverEntity): """Representation of a deCONZ cover.""" TYPE = DOMAIN - _device: Cover def __init__(self, cover_id: str, gateway: DeconzGateway) -> None: """Set up cover device.""" diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py index e9be4658fb5..0ac7acf5b49 100644 --- a/homeassistant/components/deconz/deconz_device.py +++ b/homeassistant/components/deconz/deconz_device.py @@ -2,10 +2,13 @@ from __future__ import annotations -from pydeconz.models.group import Group as DeconzGroup -from pydeconz.models.light import LightBase as DeconzLight +from typing import Generic, TypeVar, Union + +from pydeconz.models.deconz_device import DeconzDevice as PydeconzDevice +from pydeconz.models.group import Group as PydeconzGroup +from pydeconz.models.light import LightBase as PydeconzLightBase from pydeconz.models.scene import Scene as PydeconzScene -from pydeconz.models.sensor import SensorBase as DeconzSensor +from pydeconz.models.sensor import SensorBase as PydeconzSensorBase from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE @@ -15,29 +18,39 @@ from homeassistant.helpers.entity import DeviceInfo, Entity from .const import DOMAIN as DECONZ_DOMAIN from .gateway import DeconzGateway +_DeviceT = TypeVar( + "_DeviceT", + bound=Union[ + PydeconzGroup, + PydeconzLightBase, + PydeconzSensorBase, + PydeconzScene, + ], +) -class DeconzBase: + +class DeconzBase(Generic[_DeviceT]): """Common base for deconz entities and events.""" def __init__( self, - device: DeconzGroup | DeconzLight | DeconzSensor | PydeconzScene, + device: _DeviceT, gateway: DeconzGateway, ) -> None: """Set up device and add update callback to get data from websocket.""" - self._device = device + self._device: _DeviceT = device self.gateway = gateway @property def unique_id(self) -> str: """Return a unique identifier for this device.""" - assert not isinstance(self._device, PydeconzScene) + assert isinstance(self._device, PydeconzDevice) return self._device.unique_id @property def serial(self) -> str | None: """Return a serial number for this device.""" - assert not isinstance(self._device, PydeconzScene) + assert isinstance(self._device, PydeconzDevice) if not self._device.unique_id or self._device.unique_id.count(":") != 7: return None return self._device.unique_id.split("-", 1)[0] @@ -45,7 +58,7 @@ class DeconzBase: @property def device_info(self) -> DeviceInfo | None: """Return a device description for device registry.""" - assert not isinstance(self._device, PydeconzScene) + assert isinstance(self._device, PydeconzDevice) if self.serial is None: return None @@ -60,7 +73,7 @@ class DeconzBase: ) -class DeconzDevice(DeconzBase, Entity): +class DeconzDevice(DeconzBase[_DeviceT], Entity): """Representation of a deCONZ device.""" _attr_should_poll = False @@ -69,7 +82,7 @@ class DeconzDevice(DeconzBase, Entity): def __init__( self, - device: DeconzGroup | DeconzLight | DeconzSensor | PydeconzScene, + device: _DeviceT, gateway: DeconzGateway, ) -> None: """Set up device and add update callback to get data from websocket.""" @@ -114,16 +127,14 @@ class DeconzDevice(DeconzBase, Entity): """Return True if device is available.""" if isinstance(self._device, PydeconzScene): return self.gateway.available - return self.gateway.available and self._device.reachable + return self.gateway.available and self._device.reachable # type: ignore[union-attr] -class DeconzSceneMixin(DeconzDevice): +class DeconzSceneMixin(DeconzDevice[PydeconzScene]): """Representation of a deCONZ scene.""" _attr_has_entity_name = True - _device: PydeconzScene - def __init__( self, device: PydeconzScene, diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 6002e61d326..a0d62126b92 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -49,11 +49,10 @@ async def async_setup_entry( ) -class DeconzFan(DeconzDevice, FanEntity): +class DeconzFan(DeconzDevice[Light], FanEntity): """Representation of a deCONZ fan.""" TYPE = DOMAIN - _device: Light _default_on_speed = LightFanSpeed.PERCENT_50 _attr_supported_features = FanEntityFeature.SET_SPEED diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 7c6f1a0e362..590c0795e65 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, Generic, TypedDict, TypeVar +from typing import Any, TypedDict, TypeVar, Union from pydeconz.interfaces.groups import GroupHandler from pydeconz.interfaces.lights import LightHandler @@ -47,7 +47,7 @@ DECONZ_TO_COLOR_MODE = { LightColorMode.XY: ColorMode.XY, } -_L = TypeVar("_L", Group, Light) +_LightDeviceT = TypeVar("_LightDeviceT", bound=Union[Group, Light]) class SetStateAttributes(TypedDict, total=False): @@ -121,14 +121,12 @@ async def async_setup_entry( ) -class DeconzBaseLight(Generic[_L], DeconzDevice, LightEntity): +class DeconzBaseLight(DeconzDevice[_LightDeviceT], LightEntity): """Representation of a deCONZ light.""" TYPE = DOMAIN - _device: _L - - def __init__(self, device: _L, gateway: DeconzGateway) -> None: + def __init__(self, device: _LightDeviceT, gateway: DeconzGateway) -> None: """Set up light.""" super().__init__(device, gateway) @@ -261,8 +259,6 @@ class DeconzBaseLight(Generic[_L], DeconzDevice, LightEntity): class DeconzLight(DeconzBaseLight[Light]): """Representation of a deCONZ light.""" - _device: Light - @property def max_mireds(self) -> int: """Return the warmest color_temp that this light supports.""" @@ -289,8 +285,6 @@ class DeconzGroup(DeconzBaseLight[Group]): _attr_has_entity_name = True - _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}" diff --git a/homeassistant/components/deconz/lock.py b/homeassistant/components/deconz/lock.py index d6f9d670c01..9c4c5b43dbe 100644 --- a/homeassistant/components/deconz/lock.py +++ b/homeassistant/components/deconz/lock.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any +from typing import Any, Union from pydeconz.models.event import EventType from pydeconz.models.light.lock import Lock @@ -50,11 +50,10 @@ async def async_setup_entry( ) -class DeconzLock(DeconzDevice, LockEntity): +class DeconzLock(DeconzDevice[Union[DoorLock, Lock]], LockEntity): """Representation of a deCONZ lock.""" TYPE = DOMAIN - _device: DoorLock | Lock @property def is_locked(self) -> bool: diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index 4c0959f950d..636711d609d 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -81,11 +81,10 @@ async def async_setup_entry( ) -class DeconzNumber(DeconzDevice, NumberEntity): +class DeconzNumber(DeconzDevice[Presence], NumberEntity): """Representation of a deCONZ number entity.""" TYPE = DOMAIN - _device: Presence def __init__( self, diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 15af1b3dd8f..941729ac8c2 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -273,11 +273,10 @@ async def async_setup_entry( ) -class DeconzSensor(DeconzDevice, SensorEntity): +class DeconzSensor(DeconzDevice[SensorResources], SensorEntity): """Representation of a deCONZ sensor.""" TYPE = DOMAIN - _device: SensorResources entity_description: DeconzSensorDescription def __init__( diff --git a/homeassistant/components/deconz/siren.py b/homeassistant/components/deconz/siren.py index d44bce01aad..45c81c9e31c 100644 --- a/homeassistant/components/deconz/siren.py +++ b/homeassistant/components/deconz/siren.py @@ -41,7 +41,7 @@ async def async_setup_entry( ) -class DeconzSiren(DeconzDevice, SirenEntity): +class DeconzSiren(DeconzDevice[Siren], SirenEntity): """Representation of a deCONZ siren.""" TYPE = DOMAIN @@ -50,7 +50,6 @@ class DeconzSiren(DeconzDevice, SirenEntity): | SirenEntityFeature.TURN_OFF | SirenEntityFeature.DURATION ) - _device: Siren @property def is_on(self) -> bool: diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index b21ec929909..990de24dffc 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -43,11 +43,10 @@ async def async_setup_entry( ) -class DeconzPowerPlug(DeconzDevice, SwitchEntity): +class DeconzPowerPlug(DeconzDevice[Light], SwitchEntity): """Representation of a deCONZ power plug.""" TYPE = DOMAIN - _device: Light @property def is_on(self) -> bool: