From 0339761e7264d77d3955a62bebd02bd380588a68 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 17 Nov 2021 15:11:51 +0100 Subject: [PATCH] Add typing to deCONZ Number and Sensor platforms (#59604) --- homeassistant/components/deconz/number.py | 53 +++++++++++----- homeassistant/components/deconz/sensor.py | 75 ++++++++++++++--------- 2 files changed, 84 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index 0ac355f7dd1..6c3ca08710c 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import ValuesView from dataclasses import dataclass from pydeconz.sensor import PRESENCE_DELAY, Presence @@ -11,25 +12,35 @@ from homeassistant.components.number import ( NumberEntity, NumberEntityDescription, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ENTITY_CATEGORY_CONFIG -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .gateway import get_gateway_from_config_entry +from .gateway import DeconzGateway, get_gateway_from_config_entry @dataclass -class DeconzNumberEntityDescription(NumberEntityDescription): +class DeconzNumberEntityDescriptionBase: + """Required values when describing deCONZ number entities.""" + + device_property: str + suffix: str + update_key: str + max_value: int + min_value: int + step: int + + +@dataclass +class DeconzNumberEntityDescription( + NumberEntityDescription, DeconzNumberEntityDescriptionBase +): """Class describing deCONZ number entities.""" entity_category = ENTITY_CATEGORY_CONFIG - device_property: str | None = None - suffix: str | None = None - update_key: str | None = None - max_value: int | None = None - min_value: int | None = None - step: int | None = None ENTITY_DESCRIPTIONS = { @@ -47,13 +58,19 @@ ENTITY_DESCRIPTIONS = { } -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 number entity.""" gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() @callback - def async_add_sensor(sensors=gateway.api.sensors.values()): + def async_add_sensor( + sensors: list[Presence] | ValuesView[Presence] = gateway.api.sensors.values(), + ) -> None: """Add number config sensor from deCONZ.""" entities = [] @@ -92,13 +109,19 @@ class DeconzNumber(DeconzDevice, NumberEntity): """Representation of a deCONZ number entity.""" TYPE = DOMAIN + _device: Presence - def __init__(self, device, gateway, description): + def __init__( + self, + device: Presence, + gateway: DeconzGateway, + description: DeconzNumberEntityDescription, + ) -> None: """Initialize deCONZ number entity.""" - self.entity_description = description + self.entity_description: DeconzNumberEntityDescription = description super().__init__(device, gateway) - self._attr_name = f"{self._device.name} {description.suffix}" + self._attr_name = f"{device.name} {description.suffix}" self._attr_max_value = description.max_value self._attr_min_value = description.min_value self._attr_step = description.step @@ -113,7 +136,7 @@ class DeconzNumber(DeconzDevice, NumberEntity): @property def value(self) -> float: """Return the value of the sensor property.""" - return getattr(self._device, self.entity_description.device_property) + return getattr(self._device, self.entity_description.device_property) # type: ignore[no-any-return] async def async_set_value(self, value: float) -> None: """Set sensor config.""" diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 3f8c22d43d6..e530e33e654 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -1,9 +1,14 @@ """Support for deCONZ sensors.""" +from __future__ import annotations + +from collections.abc import ValuesView + from pydeconz.sensor import ( AirQuality, Battery, Consumption, Daylight, + DeconzSensor as PydeconzSensor, GenericStatus, Humidity, LightLevel, @@ -22,6 +27,7 @@ from homeassistant.components.sensor import ( SensorEntity, SensorEntityDescription, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_TEMPERATURE, ATTR_VOLTAGE, @@ -40,15 +46,17 @@ from homeassistant.const import ( PRESSURE_HPA, TEMP_CELSIUS, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType from .const import ATTR_DARK, ATTR_ON from .deconz_device import DeconzDevice -from .gateway import get_gateway_from_config_entry +from .gateway import DeconzGateway, get_gateway_from_config_entry DECONZ_SENSORS = ( AirQuality, @@ -119,7 +127,11 @@ ENTITY_DESCRIPTIONS = { } -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 sensors.""" gateway = get_gateway_from_config_entry(hass, config_entry) gateway.entities[DOMAIN] = set() @@ -127,13 +139,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): battery_handler = DeconzBatteryHandler(gateway) @callback - def async_add_sensor(sensors=gateway.api.sensors.values()): + def async_add_sensor( + sensors: list[PydeconzSensor] + | ValuesView[PydeconzSensor] = gateway.api.sensors.values(), + ) -> None: """Add sensors from deCONZ. Create DeconzBattery if sensor has a battery attribute. Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor. """ - entities = [] + entities: list[DeconzBattery | DeconzSensor | DeconzTemperature] = [] for sensor in sensors: @@ -184,8 +199,9 @@ class DeconzSensor(DeconzDevice, SensorEntity): """Representation of a deCONZ sensor.""" TYPE = DOMAIN + _device: PydeconzSensor - def __init__(self, device, gateway): + def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None: """Initialize deCONZ binary sensor.""" super().__init__(device, gateway) @@ -193,19 +209,19 @@ class DeconzSensor(DeconzDevice, SensorEntity): self.entity_description = entity_description @callback - def async_update_callback(self): + def async_update_callback(self) -> None: """Update the sensor's state.""" keys = {"on", "reachable", "state"} if self._device.changed_keys.intersection(keys): super().async_update_callback() @property - def native_value(self): + def native_value(self) -> StateType: """Return the state of the sensor.""" - return self._device.state + return self._device.state # type: ignore[no-any-return] @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, bool | float | int | None]: """Return the state attributes of the sensor.""" attr = {} @@ -243,8 +259,9 @@ class DeconzTemperature(DeconzDevice, SensorEntity): """ TYPE = DOMAIN + _device: PydeconzSensor - def __init__(self, device, gateway): + def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None: """Initialize deCONZ temperature sensor.""" super().__init__(device, gateway) @@ -252,29 +269,30 @@ class DeconzTemperature(DeconzDevice, SensorEntity): self._attr_name = f"{self._device.name} Temperature" @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique identifier for this device.""" return f"{self.serial}-temperature" @callback - def async_update_callback(self): + def async_update_callback(self) -> None: """Update the sensor's state.""" keys = {"temperature", "reachable"} if self._device.changed_keys.intersection(keys): super().async_update_callback() @property - def native_value(self): + def native_value(self) -> StateType: """Return the state of the sensor.""" - return self._device.secondary_temperature + return self._device.secondary_temperature # type: ignore[no-any-return] class DeconzBattery(DeconzDevice, SensorEntity): """Battery class for when a device is only represented as an event.""" TYPE = DOMAIN + _device: PydeconzSensor - def __init__(self, device, gateway): + def __init__(self, device: PydeconzSensor, gateway: DeconzGateway) -> None: """Initialize deCONZ battery level sensor.""" super().__init__(device, gateway) @@ -282,14 +300,14 @@ class DeconzBattery(DeconzDevice, SensorEntity): self._attr_name = f"{self._device.name} Battery Level" @callback - def async_update_callback(self): + def async_update_callback(self) -> None: """Update the battery's state, if needed.""" keys = {"battery", "reachable"} if self._device.changed_keys.intersection(keys): super().async_update_callback() @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique identifier for this device. Normally there should only be one battery sensor per device from deCONZ. @@ -305,12 +323,12 @@ class DeconzBattery(DeconzDevice, SensorEntity): return f"{self.serial}-battery" @property - def native_value(self): + def native_value(self) -> StateType: """Return the state of the battery.""" - return self._device.battery + return self._device.battery # type: ignore[no-any-return] @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, str]: """Return the state attributes of the battery.""" attr = {} @@ -325,21 +343,20 @@ class DeconzBattery(DeconzDevice, SensorEntity): class DeconzSensorStateTracker: """Track sensors without a battery state and signal when battery state exist.""" - def __init__(self, sensor, gateway): + def __init__(self, sensor: PydeconzSensor, gateway: DeconzGateway) -> None: """Set up tracker.""" self.sensor = sensor self.gateway = gateway sensor.register_callback(self.async_update_callback) @callback - def close(self): + def close(self) -> None: """Clean up tracker.""" self.sensor.remove_callback(self.async_update_callback) - self.gateway = None self.sensor = None @callback - def async_update_callback(self): + def async_update_callback(self) -> None: """Sensor state updated.""" if "battery" in self.sensor.changed_keys: async_dispatcher_send( @@ -352,13 +369,13 @@ class DeconzSensorStateTracker: class DeconzBatteryHandler: """Creates and stores trackers for sensors without a battery state.""" - def __init__(self, gateway): + def __init__(self, gateway: DeconzGateway) -> None: """Set up battery handler.""" self.gateway = gateway - self._trackers = set() + self._trackers: set[DeconzSensorStateTracker] = set() @callback - def create_tracker(self, sensor): + def create_tracker(self, sensor: PydeconzSensor) -> None: """Create new tracker for battery state.""" for tracker in self._trackers: if sensor == tracker.sensor: @@ -366,7 +383,7 @@ class DeconzBatteryHandler: self._trackers.add(DeconzSensorStateTracker(sensor, self.gateway)) @callback - def remove_tracker(self, sensor): + def remove_tracker(self, sensor: PydeconzSensor) -> None: """Remove tracker of battery state.""" for tracker in self._trackers: if sensor == tracker.sensor: