From caa1ba7e13ff6bbec66069381e3b7d2a30aae6c5 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 26 Jan 2023 22:03:36 +0100 Subject: [PATCH] Improve nuki typing (#86736) * Use NukiCoordinator * Make NukiEntity generic * Remove unnecessary ABC --- homeassistant/components/nuki/__init__.py | 67 ++++++++++--------- .../components/nuki/binary_sensor.py | 9 +-- homeassistant/components/nuki/lock.py | 22 +++--- homeassistant/components/nuki/sensor.py | 5 +- 4 files changed, 54 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/nuki/__init__.py b/homeassistant/components/nuki/__init__.py index 20309339451..6a3248385ad 100644 --- a/homeassistant/components/nuki/__init__.py +++ b/homeassistant/components/nuki/__init__.py @@ -1,7 +1,10 @@ """The nuki component.""" +from __future__ import annotations + from collections import defaultdict from datetime import timedelta import logging +from typing import Generic, TypeVar import async_timeout from pynuki import NukiBridge, NukiLock, NukiOpener @@ -31,6 +34,8 @@ from .const import ( ) from .helpers import parse_id +_NukiDeviceT = TypeVar("_NukiDeviceT", bound=NukiDevice) + _LOGGER = logging.getLogger(__name__) PLATFORMS = [Platform.BINARY_SENSOR, Platform.LOCK, Platform.SENSOR] @@ -109,38 +114,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -class NukiEntity(CoordinatorEntity[DataUpdateCoordinator[None]]): - """An entity using CoordinatorEntity. - - The CoordinatorEntity class provides: - should_poll - async_update - async_added_to_hass - available - - """ - - def __init__( - self, coordinator: DataUpdateCoordinator[None], nuki_device: NukiDevice - ) -> None: - """Pass coordinator to CoordinatorEntity.""" - super().__init__(coordinator) - self._nuki_device = nuki_device - - @property - def device_info(self): - """Device info for Nuki entities.""" - return { - "identifiers": {(DOMAIN, parse_id(self._nuki_device.nuki_id))}, - "name": self._nuki_device.name, - "manufacturer": "Nuki Home Solutions GmbH", - "model": self._nuki_device.device_type_str.capitalize(), - "sw_version": self._nuki_device.firmware_version, - "via_device": (DOMAIN, self.coordinator.bridge_id), - } - - -class NukiCoordinator(DataUpdateCoordinator): +class NukiCoordinator(DataUpdateCoordinator[None]): """Data Update Coordinator for the Nuki integration.""" def __init__(self, hass, bridge, locks, openers): @@ -217,3 +191,32 @@ class NukiCoordinator(DataUpdateCoordinator): break return events + + +class NukiEntity(CoordinatorEntity[NukiCoordinator], Generic[_NukiDeviceT]): + """An entity using CoordinatorEntity. + + The CoordinatorEntity class provides: + should_poll + async_update + async_added_to_hass + available + + """ + + def __init__(self, coordinator: NukiCoordinator, nuki_device: _NukiDeviceT) -> None: + """Pass coordinator to CoordinatorEntity.""" + super().__init__(coordinator) + self._nuki_device = nuki_device + + @property + def device_info(self): + """Device info for Nuki entities.""" + return { + "identifiers": {(DOMAIN, parse_id(self._nuki_device.nuki_id))}, + "name": self._nuki_device.name, + "manufacturer": "Nuki Home Solutions GmbH", + "model": self._nuki_device.device_type_str.capitalize(), + "sw_version": self._nuki_device.firmware_version, + "via_device": (DOMAIN, self.coordinator.bridge_id), + } diff --git a/homeassistant/components/nuki/binary_sensor.py b/homeassistant/components/nuki/binary_sensor.py index 6e73d9c3208..93bf164acc5 100644 --- a/homeassistant/components/nuki/binary_sensor.py +++ b/homeassistant/components/nuki/binary_sensor.py @@ -1,6 +1,8 @@ """Doorsensor Support for the Nuki Lock.""" +from __future__ import annotations from pynuki.constants import STATE_DOORSENSOR_OPENED +from pynuki.device import NukiDevice from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -9,9 +11,8 @@ from homeassistant.components.binary_sensor import ( from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import NukiEntity +from . import NukiCoordinator, NukiEntity from .const import ATTR_NUKI_ID, DATA_COORDINATOR, DATA_LOCKS, DOMAIN as NUKI_DOMAIN @@ -20,7 +21,7 @@ async def async_setup_entry( ) -> None: """Set up the Nuki lock binary sensor.""" data = hass.data[NUKI_DOMAIN][entry.entry_id] - coordinator: DataUpdateCoordinator[None] = data[DATA_COORDINATOR] + coordinator: NukiCoordinator = data[DATA_COORDINATOR] entities = [] @@ -31,7 +32,7 @@ async def async_setup_entry( async_add_entities(entities) -class NukiDoorsensorEntity(NukiEntity, BinarySensorEntity): +class NukiDoorsensorEntity(NukiEntity[NukiDevice], BinarySensorEntity): """Representation of a Nuki Lock Doorsensor.""" _attr_has_entity_name = True diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 45fbf726e7a..56b19b75a69 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -1,11 +1,12 @@ """Nuki.io lock platform.""" from __future__ import annotations -from abc import ABC, abstractmethod -from typing import Any +from abc import abstractmethod +from typing import Any, TypeVar from pynuki import NukiLock, NukiOpener from pynuki.constants import MODE_OPENER_CONTINUOUS +from pynuki.device import NukiDevice from requests.exceptions import RequestException import voluptuous as vol @@ -14,9 +15,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import NukiEntity +from . import NukiCoordinator, NukiEntity from .const import ( ATTR_BATTERY_CRITICAL, ATTR_ENABLE, @@ -30,13 +30,15 @@ from .const import ( ) from .helpers import CannotConnect +_NukiDeviceT = TypeVar("_NukiDeviceT", bound=NukiDevice) + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the Nuki lock platform.""" data = hass.data[NUKI_DOMAIN][entry.entry_id] - coordinator: DataUpdateCoordinator[None] = data[DATA_COORDINATOR] + coordinator: NukiCoordinator = data[DATA_COORDINATOR] entities: list[NukiDeviceEntity] = [ NukiLockEntity(coordinator, lock) for lock in data[DATA_LOCKS] @@ -64,7 +66,7 @@ async def async_setup_entry( ) -class NukiDeviceEntity(NukiEntity, LockEntity, ABC): +class NukiDeviceEntity(NukiEntity[_NukiDeviceT], LockEntity): """Representation of a Nuki device.""" _attr_has_entity_name = True @@ -101,11 +103,9 @@ class NukiDeviceEntity(NukiEntity, LockEntity, ABC): """Open the door latch.""" -class NukiLockEntity(NukiDeviceEntity): +class NukiLockEntity(NukiDeviceEntity[NukiLock]): """Representation of a Nuki lock.""" - _nuki_device: NukiLock - @property def is_locked(self) -> bool: """Return true if lock is locked.""" @@ -144,11 +144,9 @@ class NukiLockEntity(NukiDeviceEntity): raise CannotConnect from err -class NukiOpenerEntity(NukiDeviceEntity): +class NukiOpenerEntity(NukiDeviceEntity[NukiOpener]): """Representation of a Nuki opener.""" - _nuki_device: NukiOpener - @property def is_locked(self) -> bool: """Return true if either ring-to-open or continuous mode is enabled.""" diff --git a/homeassistant/components/nuki/sensor.py b/homeassistant/components/nuki/sensor.py index f4a9103eecb..04768c49b11 100644 --- a/homeassistant/components/nuki/sensor.py +++ b/homeassistant/components/nuki/sensor.py @@ -1,4 +1,7 @@ """Battery sensor for the Nuki Lock.""" +from __future__ import annotations + +from pynuki.device import NukiDevice from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry @@ -23,7 +26,7 @@ async def async_setup_entry( ) -class NukiBatterySensor(NukiEntity, SensorEntity): +class NukiBatterySensor(NukiEntity[NukiDevice], SensorEntity): """Representation of a Nuki Lock Battery sensor.""" _attr_has_entity_name = True