From 5ae419367e0d2f2632193ee99f72f6022568dbb0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:59:31 +0100 Subject: [PATCH] Enable strict typing for generic_hygrostat (#107272) --- .strict-typing | 1 + .../generic_hygrostat/humidifier.py | 151 ++++++++++-------- mypy.ini | 10 ++ 3 files changed, 95 insertions(+), 67 deletions(-) diff --git a/.strict-typing b/.strict-typing index 8aa14e5b40d..33e608d38c8 100644 --- a/.strict-typing +++ b/.strict-typing @@ -177,6 +177,7 @@ homeassistant.components.fritzbox_callmonitor.* homeassistant.components.fronius.* homeassistant.components.frontend.* homeassistant.components.fully_kiosk.* +homeassistant.components.generic_hygrostat.* homeassistant.components.geo_location.* homeassistant.components.geocaching.* homeassistant.components.gios.* diff --git a/homeassistant/components/generic_hygrostat/humidifier.py b/homeassistant/components/generic_hygrostat/humidifier.py index 3bdecbfa997..095b46245cf 100644 --- a/homeassistant/components/generic_hygrostat/humidifier.py +++ b/homeassistant/components/generic_hygrostat/humidifier.py @@ -2,7 +2,10 @@ from __future__ import annotations import asyncio +from collections.abc import Callable +from datetime import datetime, timedelta import logging +from typing import TYPE_CHECKING, Any from homeassistant.components.humidifier import ( ATTR_HUMIDITY, @@ -27,7 +30,13 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant, callback +from homeassistant.core import ( + DOMAIN as HA_DOMAIN, + Event, + HomeAssistant, + State, + callback, +) from homeassistant.helpers import condition from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import ( @@ -72,22 +81,22 @@ async def async_setup_platform( """Set up the generic hygrostat platform.""" if discovery_info: config = discovery_info - name = config[CONF_NAME] - switch_entity_id = config[CONF_HUMIDIFIER] - sensor_entity_id = config[CONF_SENSOR] - min_humidity = config.get(CONF_MIN_HUMIDITY) - max_humidity = config.get(CONF_MAX_HUMIDITY) - target_humidity = config.get(CONF_TARGET_HUMIDITY) - device_class = config.get(CONF_DEVICE_CLASS) - min_cycle_duration = config.get(CONF_MIN_DUR) - sensor_stale_duration = config.get(CONF_STALE_DURATION) - dry_tolerance = config[CONF_DRY_TOLERANCE] - wet_tolerance = config[CONF_WET_TOLERANCE] - keep_alive = config.get(CONF_KEEP_ALIVE) - initial_state = config.get(CONF_INITIAL_STATE) - away_humidity = config.get(CONF_AWAY_HUMIDITY) - away_fixed = config.get(CONF_AWAY_FIXED) - unique_id = config.get(CONF_UNIQUE_ID) + name: str = config[CONF_NAME] + switch_entity_id: str = config[CONF_HUMIDIFIER] + sensor_entity_id: str = config[CONF_SENSOR] + min_humidity: int | None = config.get(CONF_MIN_HUMIDITY) + max_humidity: int | None = config.get(CONF_MAX_HUMIDITY) + target_humidity: int | None = config.get(CONF_TARGET_HUMIDITY) + device_class: HumidifierDeviceClass | None = config.get(CONF_DEVICE_CLASS) + min_cycle_duration: timedelta | None = config.get(CONF_MIN_DUR) + sensor_stale_duration: timedelta | None = config.get(CONF_STALE_DURATION) + dry_tolerance: float = config[CONF_DRY_TOLERANCE] + wet_tolerance: float = config[CONF_WET_TOLERANCE] + keep_alive: timedelta | None = config.get(CONF_KEEP_ALIVE) + initial_state: bool | None = config.get(CONF_INITIAL_STATE) + away_humidity: int | None = config.get(CONF_AWAY_HUMIDITY) + away_fixed: bool | None = config.get(CONF_AWAY_FIXED) + unique_id: str | None = config.get(CONF_UNIQUE_ID) async_add_entities( [ @@ -120,28 +129,28 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): def __init__( self, - name, - switch_entity_id, - sensor_entity_id, - min_humidity, - max_humidity, - target_humidity, - device_class, - min_cycle_duration, - dry_tolerance, - wet_tolerance, - keep_alive, - initial_state, - away_humidity, - away_fixed, - sensor_stale_duration, - unique_id, - ): + name: str, + switch_entity_id: str, + sensor_entity_id: str, + min_humidity: int | None, + max_humidity: int | None, + target_humidity: int | None, + device_class: HumidifierDeviceClass | None, + min_cycle_duration: timedelta | None, + dry_tolerance: float, + wet_tolerance: float, + keep_alive: timedelta | None, + initial_state: bool | None, + away_humidity: int | None, + away_fixed: bool | None, + sensor_stale_duration: timedelta | None, + unique_id: str | None, + ) -> None: """Initialize the hygrostat.""" self._name = name self._switch_entity_id = switch_entity_id self._sensor_entity_id = sensor_entity_id - self._device_class = device_class + self._device_class = device_class or HumidifierDeviceClass.HUMIDIFIER self._min_cycle_duration = min_cycle_duration self._dry_tolerance = dry_tolerance self._wet_tolerance = wet_tolerance @@ -149,7 +158,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): self._state = initial_state self._saved_target_humidity = away_humidity or target_humidity self._active = False - self._cur_humidity = None + self._cur_humidity: float | None = None self._humidity_lock = asyncio.Lock() self._min_humidity = min_humidity self._max_humidity = max_humidity @@ -159,14 +168,12 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): self._away_humidity = away_humidity self._away_fixed = away_fixed self._sensor_stale_duration = sensor_stale_duration - self._remove_stale_tracking = None + self._remove_stale_tracking: Callable[[], None] | None = None self._is_away = False - if not self._device_class: - self._device_class = HumidifierDeviceClass.HUMIDIFIER self._attr_action = HumidifierAction.IDLE self._attr_unique_id = unique_id - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Run when entity about to be added.""" await super().async_added_to_hass() @@ -185,7 +192,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): ) ) - async def _async_startup(event): + async def _async_startup(event: Event | None) -> None: """Init on startup.""" sensor_state = self.hass.states.get(self._sensor_entity_id) if sensor_state is None or sensor_state.state in ( @@ -234,39 +241,39 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): return await super().async_will_remove_from_hass() @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return self._active @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any] | None: """Return the optional state attributes.""" if self._saved_target_humidity: return {ATTR_SAVED_HUMIDITY: self._saved_target_humidity} return None @property - def name(self): + def name(self) -> str: """Return the name of the hygrostat.""" return self._name @property - def is_on(self): + def is_on(self) -> bool | None: """Return true if the hygrostat is on.""" return self._state @property - def current_humidity(self): + def current_humidity(self) -> int | None: """Return the measured humidity.""" - return self._cur_humidity + return int(self._cur_humidity) if self._cur_humidity is not None else None @property - def target_humidity(self): + def target_humidity(self) -> int | None: """Return the humidity we try to reach.""" return self._target_humidity @property - def mode(self): + def mode(self) -> str | None: """Return the current mode.""" if self._away_humidity is None: return None @@ -275,18 +282,18 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): return MODE_NORMAL @property - def available_modes(self): + def available_modes(self) -> list[str] | None: """Return a list of available modes.""" if self._away_humidity: return [MODE_NORMAL, MODE_AWAY] return None @property - def device_class(self): + def device_class(self) -> HumidifierDeviceClass: """Return the device class of the humidifier.""" return self._device_class - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Turn hygrostat on.""" if not self._active: return @@ -294,7 +301,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): await self._async_operate(force=True) self.async_write_ha_state() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Turn hygrostat off.""" if not self._active: return @@ -306,7 +313,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" if humidity is None: - return + return # type: ignore[unreachable] if self._is_away and self._away_fixed: self._saved_target_humidity = humidity @@ -318,7 +325,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): self.async_write_ha_state() @property - def min_humidity(self): + def min_humidity(self) -> int: """Return the minimum humidity.""" if self._min_humidity: return self._min_humidity @@ -327,7 +334,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): return super().min_humidity @property - def max_humidity(self): + def max_humidity(self) -> int: """Return the maximum humidity.""" if self._max_humidity: return self._max_humidity @@ -335,7 +342,9 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): # Get default humidity from super class return super().max_humidity - async def _async_sensor_changed(self, entity_id, old_state, new_state): + async def _async_sensor_changed( + self, entity_id: str, old_state: State | None, new_state: State | None + ) -> None: """Handle ambient humidity changes.""" if new_state is None: return @@ -353,18 +362,21 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): await self._async_operate() self.async_write_ha_state() - async def _async_sensor_not_responding(self, now=None): + async def _async_sensor_not_responding(self, now: datetime | None = None) -> None: """Handle sensor stale event.""" + state = self.hass.states.get(self._sensor_entity_id) _LOGGER.debug( "Sensor has not been updated for %s", - now - self.hass.states.get(self._sensor_entity_id).last_updated, + now - state.last_updated if now and state else "---", ) _LOGGER.warning("Sensor is stalled, call the emergency stop") await self._async_update_humidity("Stalled") @callback - def _async_switch_changed(self, entity_id, old_state, new_state): + def _async_switch_changed( + self, entity_id: str, old_state: State | None, new_state: State | None + ) -> None: """Handle humidifier switch state changes.""" if new_state is None: return @@ -379,7 +391,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): self.async_schedule_update_ha_state() - async def _async_update_humidity(self, humidity): + async def _async_update_humidity(self, humidity: str) -> None: """Update hygrostat with latest state from sensor.""" try: self._cur_humidity = float(humidity) @@ -390,7 +402,9 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): if self._is_device_active: await self._async_device_turn_off() - async def _async_operate(self, time=None, force=False): + async def _async_operate( + self, time: datetime | None = None, force: bool = False + ) -> None: """Check if we need to turn humidifying on or off.""" async with self._humidity_lock: if not self._active and None not in ( @@ -432,12 +446,15 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): if force: # Ignore the tolerance when switched on manually - dry_tolerance = 0 - wet_tolerance = 0 + dry_tolerance: float = 0 + wet_tolerance: float = 0 else: dry_tolerance = self._dry_tolerance wet_tolerance = self._wet_tolerance + if TYPE_CHECKING: + assert self._target_humidity is not None + assert self._cur_humidity is not None too_dry = self._target_humidity - self._cur_humidity >= dry_tolerance too_wet = self._cur_humidity - self._target_humidity >= wet_tolerance if self._is_device_active: @@ -461,16 +478,16 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): await self._async_device_turn_off() @property - def _is_device_active(self): + def _is_device_active(self) -> bool: """If the toggleable device is currently active.""" return self.hass.states.is_state(self._switch_entity_id, STATE_ON) - async def _async_device_turn_on(self): + async def _async_device_turn_on(self) -> None: """Turn humidifier toggleable device on.""" data = {ATTR_ENTITY_ID: self._switch_entity_id} await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_ON, data) - async def _async_device_turn_off(self): + async def _async_device_turn_off(self) -> None: """Turn humidifier toggleable device off.""" data = {ATTR_ENTITY_ID: self._switch_entity_id} await self.hass.services.async_call(HA_DOMAIN, SERVICE_TURN_OFF, data) diff --git a/mypy.ini b/mypy.ini index f1bf9abbb9d..e68048c8001 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1531,6 +1531,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.generic_hygrostat.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.geo_location.*] check_untyped_defs = true disallow_incomplete_defs = true