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>
This commit is contained in:
epenet 2022-08-01 19:18:29 +02:00 committed by GitHub
parent 567f181a21
commit deff0ad61e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 41 additions and 46 deletions

View File

@ -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.""" """Representation of a deCONZ alarm control panel."""
TYPE = DOMAIN TYPE = DOMAIN
_device: AncillaryControl
_attr_code_format = CodeFormat.NUMBER _attr_code_format = CodeFormat.NUMBER
_attr_supported_features = ( _attr_supported_features = (

View File

@ -233,11 +233,10 @@ async def async_setup_entry(
) )
class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): class DeconzBinarySensor(DeconzDevice[SensorResources], BinarySensorEntity):
"""Representation of a deCONZ binary sensor.""" """Representation of a deCONZ binary sensor."""
TYPE = DOMAIN TYPE = DOMAIN
_device: SensorResources
entity_description: DeconzBinarySensorDescription entity_description: DeconzBinarySensorDescription
def __init__( def __init__(

View File

@ -92,11 +92,10 @@ async def async_setup_entry(
) )
class DeconzThermostat(DeconzDevice, ClimateEntity): class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity):
"""Representation of a deCONZ thermostat.""" """Representation of a deCONZ thermostat."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Thermostat
_attr_temperature_unit = TEMP_CELSIUS _attr_temperature_unit = TEMP_CELSIUS

View File

@ -49,11 +49,10 @@ async def async_setup_entry(
) )
class DeconzCover(DeconzDevice, CoverEntity): class DeconzCover(DeconzDevice[Cover], CoverEntity):
"""Representation of a deCONZ cover.""" """Representation of a deCONZ cover."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Cover
def __init__(self, cover_id: str, gateway: DeconzGateway) -> None: def __init__(self, cover_id: str, gateway: DeconzGateway) -> None:
"""Set up cover device.""" """Set up cover device."""

View File

@ -2,10 +2,13 @@
from __future__ import annotations from __future__ import annotations
from pydeconz.models.group import Group as DeconzGroup from typing import Generic, TypeVar, Union
from pydeconz.models.light import LightBase as DeconzLight
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.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.core import callback
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE 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 .const import DOMAIN as DECONZ_DOMAIN
from .gateway import DeconzGateway 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.""" """Common base for deconz entities and events."""
def __init__( def __init__(
self, self,
device: DeconzGroup | DeconzLight | DeconzSensor | PydeconzScene, device: _DeviceT,
gateway: DeconzGateway, gateway: DeconzGateway,
) -> None: ) -> None:
"""Set up device and add update callback to get data from websocket.""" """Set up device and add update callback to get data from websocket."""
self._device = device self._device: _DeviceT = device
self.gateway = gateway self.gateway = gateway
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Return a unique identifier for this device.""" """Return a unique identifier for this device."""
assert not isinstance(self._device, PydeconzScene) assert isinstance(self._device, PydeconzDevice)
return self._device.unique_id return self._device.unique_id
@property @property
def serial(self) -> str | None: def serial(self) -> str | None:
"""Return a serial number for this device.""" """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: if not self._device.unique_id or self._device.unique_id.count(":") != 7:
return None return None
return self._device.unique_id.split("-", 1)[0] return self._device.unique_id.split("-", 1)[0]
@ -45,7 +58,7 @@ class DeconzBase:
@property @property
def device_info(self) -> DeviceInfo | None: def device_info(self) -> DeviceInfo | None:
"""Return a device description for device registry.""" """Return a device description for device registry."""
assert not isinstance(self._device, PydeconzScene) assert isinstance(self._device, PydeconzDevice)
if self.serial is None: if self.serial is None:
return None return None
@ -60,7 +73,7 @@ class DeconzBase:
) )
class DeconzDevice(DeconzBase, Entity): class DeconzDevice(DeconzBase[_DeviceT], Entity):
"""Representation of a deCONZ device.""" """Representation of a deCONZ device."""
_attr_should_poll = False _attr_should_poll = False
@ -69,7 +82,7 @@ class DeconzDevice(DeconzBase, Entity):
def __init__( def __init__(
self, self,
device: DeconzGroup | DeconzLight | DeconzSensor | PydeconzScene, device: _DeviceT,
gateway: DeconzGateway, gateway: DeconzGateway,
) -> None: ) -> None:
"""Set up device and add update callback to get data from websocket.""" """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.""" """Return True if device is available."""
if isinstance(self._device, PydeconzScene): if isinstance(self._device, PydeconzScene):
return self.gateway.available 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.""" """Representation of a deCONZ scene."""
_attr_has_entity_name = True _attr_has_entity_name = True
_device: PydeconzScene
def __init__( def __init__(
self, self,
device: PydeconzScene, device: PydeconzScene,

View File

@ -49,11 +49,10 @@ async def async_setup_entry(
) )
class DeconzFan(DeconzDevice, FanEntity): class DeconzFan(DeconzDevice[Light], FanEntity):
"""Representation of a deCONZ fan.""" """Representation of a deCONZ fan."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Light
_default_on_speed = LightFanSpeed.PERCENT_50 _default_on_speed = LightFanSpeed.PERCENT_50
_attr_supported_features = FanEntityFeature.SET_SPEED _attr_supported_features = FanEntityFeature.SET_SPEED

View File

@ -1,7 +1,7 @@
"""Support for deCONZ lights.""" """Support for deCONZ lights."""
from __future__ import annotations 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.groups import GroupHandler
from pydeconz.interfaces.lights import LightHandler from pydeconz.interfaces.lights import LightHandler
@ -47,7 +47,7 @@ DECONZ_TO_COLOR_MODE = {
LightColorMode.XY: ColorMode.XY, LightColorMode.XY: ColorMode.XY,
} }
_L = TypeVar("_L", Group, Light) _LightDeviceT = TypeVar("_LightDeviceT", bound=Union[Group, Light])
class SetStateAttributes(TypedDict, total=False): 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.""" """Representation of a deCONZ light."""
TYPE = DOMAIN TYPE = DOMAIN
_device: _L def __init__(self, device: _LightDeviceT, gateway: DeconzGateway) -> None:
def __init__(self, device: _L, gateway: DeconzGateway) -> None:
"""Set up light.""" """Set up light."""
super().__init__(device, gateway) super().__init__(device, gateway)
@ -261,8 +259,6 @@ class DeconzBaseLight(Generic[_L], DeconzDevice, LightEntity):
class DeconzLight(DeconzBaseLight[Light]): class DeconzLight(DeconzBaseLight[Light]):
"""Representation of a deCONZ light.""" """Representation of a deCONZ light."""
_device: Light
@property @property
def max_mireds(self) -> int: def max_mireds(self) -> int:
"""Return the warmest color_temp that this light supports.""" """Return the warmest color_temp that this light supports."""
@ -289,8 +285,6 @@ class DeconzGroup(DeconzBaseLight[Group]):
_attr_has_entity_name = True _attr_has_entity_name = True
_device: Group
def __init__(self, device: Group, gateway: DeconzGateway) -> None: def __init__(self, device: Group, gateway: DeconzGateway) -> None:
"""Set up group and create an unique id.""" """Set up group and create an unique id."""
self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}" self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}"

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any, Union
from pydeconz.models.event import EventType from pydeconz.models.event import EventType
from pydeconz.models.light.lock import Lock 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.""" """Representation of a deCONZ lock."""
TYPE = DOMAIN TYPE = DOMAIN
_device: DoorLock | Lock
@property @property
def is_locked(self) -> bool: def is_locked(self) -> bool:

View File

@ -81,11 +81,10 @@ async def async_setup_entry(
) )
class DeconzNumber(DeconzDevice, NumberEntity): class DeconzNumber(DeconzDevice[Presence], NumberEntity):
"""Representation of a deCONZ number entity.""" """Representation of a deCONZ number entity."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Presence
def __init__( def __init__(
self, self,

View File

@ -273,11 +273,10 @@ async def async_setup_entry(
) )
class DeconzSensor(DeconzDevice, SensorEntity): class DeconzSensor(DeconzDevice[SensorResources], SensorEntity):
"""Representation of a deCONZ sensor.""" """Representation of a deCONZ sensor."""
TYPE = DOMAIN TYPE = DOMAIN
_device: SensorResources
entity_description: DeconzSensorDescription entity_description: DeconzSensorDescription
def __init__( def __init__(

View File

@ -41,7 +41,7 @@ async def async_setup_entry(
) )
class DeconzSiren(DeconzDevice, SirenEntity): class DeconzSiren(DeconzDevice[Siren], SirenEntity):
"""Representation of a deCONZ siren.""" """Representation of a deCONZ siren."""
TYPE = DOMAIN TYPE = DOMAIN
@ -50,7 +50,6 @@ class DeconzSiren(DeconzDevice, SirenEntity):
| SirenEntityFeature.TURN_OFF | SirenEntityFeature.TURN_OFF
| SirenEntityFeature.DURATION | SirenEntityFeature.DURATION
) )
_device: Siren
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:

View File

@ -43,11 +43,10 @@ async def async_setup_entry(
) )
class DeconzPowerPlug(DeconzDevice, SwitchEntity): class DeconzPowerPlug(DeconzDevice[Light], SwitchEntity):
"""Representation of a deCONZ power plug.""" """Representation of a deCONZ power plug."""
TYPE = DOMAIN TYPE = DOMAIN
_device: Light
@property @property
def is_on(self) -> bool: def is_on(self) -> bool: