From 1bde91407508ca600c5c0f905140f9596731e82a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 22 Jul 2021 00:01:05 -0600 Subject: [PATCH] Ensure Guardian is strictly typed (#53253) --- .strict-typing | 1 + homeassistant/components/guardian/__init__.py | 51 +++++++++++-------- .../components/guardian/binary_sensor.py | 2 +- .../components/guardian/config_flow.py | 4 +- .../components/guardian/manifest.json | 2 +- homeassistant/components/guardian/sensor.py | 2 +- homeassistant/components/guardian/switch.py | 24 +++++---- homeassistant/components/guardian/util.py | 6 +-- mypy.ini | 14 +++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- script/hassfest/mypy_config.py | 1 - 12 files changed, 67 insertions(+), 44 deletions(-) diff --git a/.strict-typing b/.strict-typing index 98a96f98fb0..19835bfd777 100644 --- a/.strict-typing +++ b/.strict-typing @@ -43,6 +43,7 @@ homeassistant.components.fritz.* homeassistant.components.geo_location.* homeassistant.components.gios.* homeassistant.components.group.* +homeassistant.components.guardian.* homeassistant.components.history.* homeassistant.components.homeassistant.triggers.event homeassistant.components.http.* diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 8e605ca121c..96f5ed36720 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -2,6 +2,8 @@ from __future__ import annotations import asyncio +from collections.abc import Awaitable, MutableMapping +from typing import Any, cast from aioguardian import Client @@ -89,7 +91,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await paired_sensor_manager.async_process_latest_paired_sensor_uids() @callback - def async_process_paired_sensor_uids(): + def async_process_paired_sensor_uids() -> None: """Define a callback for when new paired sensor data is received.""" hass.async_create_task( paired_sensor_manager.async_process_latest_paired_sensor_uids() @@ -133,8 +135,7 @@ class PairedSensorManager: self._client = client self._entry = entry self._hass = hass - self._listeners = [] - self._paired_uids = set() + self._paired_uids: set[str] = set() async def async_pair_sensor(self, uid: str) -> None: """Add a new paired sensor coordinator.""" @@ -148,7 +149,9 @@ class PairedSensorManager: self._hass, client=self._client, api_name=f"{API_SENSOR_PAIRED_SENSOR_STATUS}_{uid}", - api_coro=lambda: self._client.sensor.paired_sensor_status(uid), + api_coro=lambda: cast( + Awaitable, self._client.sensor.paired_sensor_status(uid) + ), api_lock=self._api_lock, valve_controller_uid=self._entry.data[CONF_UID], ) @@ -208,12 +211,19 @@ class GuardianEntity(CoordinatorEntity): """Define a base Guardian entity.""" def __init__( # pylint: disable=super-init-not-called - self, entry: ConfigEntry, kind: str, name: str, device_class: str, icon: str + self, + entry: ConfigEntry, + kind: str, + name: str, + device_class: str | None, + icon: str | None, ) -> None: """Initialize.""" self._attr_device_class = device_class self._attr_device_info = {"manufacturer": "Elexa"} - self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: "Data provided by Elexa"} + self._attr_extra_state_attributes: MutableMapping[str, Any] = { + ATTR_ATTRIBUTION: "Data provided by Elexa" + } self._attr_icon = icon self._attr_name = name self._entry = entry @@ -236,16 +246,18 @@ class PairedSensorEntity(GuardianEntity): coordinator: DataUpdateCoordinator, kind: str, name: str, - device_class: str, - icon: str, + device_class: str | None, + icon: str | None, ) -> None: """Initialize.""" super().__init__(entry, kind, name, device_class, icon) paired_sensor_uid = coordinator.data["uid"] - self._attr_device_info["identifiers"] = {(DOMAIN, paired_sensor_uid)} - self._attr_device_info["name"] = f"Guardian Paired Sensor {paired_sensor_uid}" - self._attr_device_info["via_device"] = (DOMAIN, entry.data[CONF_UID]) + self._attr_device_info = { + "identifiers": {(DOMAIN, paired_sensor_uid)}, + "name": f"Guardian Paired Sensor {paired_sensor_uid}", + "via_device": (DOMAIN, entry.data[CONF_UID]), + } self._attr_name = f"Guardian Paired Sensor {paired_sensor_uid}: {name}" self._attr_unique_id = f"{paired_sensor_uid}_{kind}" self._kind = kind @@ -271,13 +283,11 @@ class ValveControllerEntity(GuardianEntity): """Initialize.""" super().__init__(entry, kind, name, device_class, icon) - self._attr_device_info["identifiers"] = {(DOMAIN, entry.data[CONF_UID])} - self._attr_device_info[ - "name" - ] = f"Guardian Valve Controller {entry.data[CONF_UID]}" - self._attr_device_info["model"] = coordinators[API_SYSTEM_DIAGNOSTICS].data[ - "firmware" - ] + self._attr_device_info = { + "identifiers": {(DOMAIN, entry.data[CONF_UID])}, + "name": f"Guardian Valve Controller {entry.data[CONF_UID]}", + "model": coordinators[API_SYSTEM_DIAGNOSTICS].data["firmware"], + } self._attr_name = f"Guardian {entry.data[CONF_UID]}: {name}" self._attr_unique_id = f"{entry.data[CONF_UID]}_{kind}" self._kind = kind @@ -304,7 +314,7 @@ class ValveControllerEntity(GuardianEntity): """Add a listener to a DataUpdateCoordinator based on the API referenced.""" @callback - def update(): + def update() -> None: """Update the entity's state.""" self._async_update_from_latest_data() self.async_write_ha_state() @@ -327,6 +337,7 @@ class ValveControllerEntity(GuardianEntity): return refresh_tasks = [ - coordinator.async_request_refresh() for coordinator in self.coordinators + coordinator.async_request_refresh() + for coordinator in self.coordinators.values() ] await asyncio.gather(*refresh_tasks) diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index 7d38a431f4c..8ce381e0456 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -78,7 +78,7 @@ async def async_setup_entry( ) ) - sensors = [] + sensors: list[PairedSensorBinarySensor | ValveControllerBinarySensor] = [] # Add all valve controller-specific binary sensors: for kind in VALVE_CONTROLLER_SENSORS: diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py index edbf4ef9c83..ccebeb99675 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -40,7 +40,7 @@ def async_get_pin_from_uid(uid: str) -> str: return uid[-4:] -async def validate_input(hass: HomeAssistant, data: dict[str, Any]): +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -60,7 +60,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize.""" - self.discovery_info = {} + self.discovery_info: dict[str, Any] = {} async def _async_set_unique_id(self, pin: str) -> None: """Set the config entry's unique ID (based on the device's 4-digit PIN).""" diff --git a/homeassistant/components/guardian/manifest.json b/homeassistant/components/guardian/manifest.json index 60411c5292b..baa7eb50e7a 100644 --- a/homeassistant/components/guardian/manifest.json +++ b/homeassistant/components/guardian/manifest.json @@ -3,7 +3,7 @@ "name": "Elexa Guardian", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/guardian", - "requirements": ["aioguardian==1.0.4"], + "requirements": ["aioguardian==1.0.8"], "zeroconf": ["_api._udp.local."], "codeowners": ["@bachya"], "iot_class": "local_polling", diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 1aaf83b8fb8..ce09bb99c60 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -78,7 +78,7 @@ async def async_setup_entry( ) ) - sensors = [] + sensors: list[PairedSensorSensor | ValveControllerSensor] = [] # Add all valve controller-specific binary sensors: for kind in VALVE_CONTROLLER_SENSORS: diff --git a/homeassistant/components/guardian/switch.py b/homeassistant/components/guardian/switch.py index ef74a35147f..f3621a72952 100644 --- a/homeassistant/components/guardian/switch.py +++ b/homeassistant/components/guardian/switch.py @@ -1,6 +1,8 @@ """Switches for the Elexa Guardian integration.""" from __future__ import annotations +from typing import Any + from aioguardian import Client from aioguardian.errors import GuardianError import voluptuous as vol @@ -95,7 +97,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): self._attr_is_on = True self._client = client - async def _async_continue_entity_setup(self): + async def _async_continue_entity_setup(self) -> None: """Register API interest (and related tasks) when the entity is added.""" self.async_add_coordinator_update_listener(API_VALVE_STATUS) @@ -127,7 +129,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): } ) - async def async_disable_ap(self): + async def async_disable_ap(self) -> None: """Disable the device's onboard access point.""" try: async with self._client: @@ -135,7 +137,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): except GuardianError as err: LOGGER.error("Error while disabling valve controller AP: %s", err) - async def async_enable_ap(self): + async def async_enable_ap(self) -> None: """Enable the device's onboard access point.""" try: async with self._client: @@ -143,7 +145,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): except GuardianError as err: LOGGER.error("Error while enabling valve controller AP: %s", err) - async def async_pair_sensor(self, *, uid): + async def async_pair_sensor(self, *, uid: str) -> None: """Add a new paired sensor.""" try: async with self._client: @@ -156,7 +158,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): self._entry.entry_id ].async_pair_sensor(uid) - async def async_reboot(self): + async def async_reboot(self) -> None: """Reboot the device.""" try: async with self._client: @@ -164,7 +166,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): except GuardianError as err: LOGGER.error("Error while rebooting valve controller: %s", err) - async def async_reset_valve_diagnostics(self): + async def async_reset_valve_diagnostics(self) -> None: """Fully reset system motor diagnostics.""" try: async with self._client: @@ -172,7 +174,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): except GuardianError as err: LOGGER.error("Error while resetting valve diagnostics: %s", err) - async def async_unpair_sensor(self, *, uid): + async def async_unpair_sensor(self, *, uid: str) -> None: """Add a new paired sensor.""" try: async with self._client: @@ -185,7 +187,9 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): self._entry.entry_id ].async_unpair_sensor(uid) - async def async_upgrade_firmware(self, *, url, port, filename): + async def async_upgrade_firmware( + self, *, url: str, port: int, filename: str + ) -> None: """Upgrade the device firmware.""" try: async with self._client: @@ -197,7 +201,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): except GuardianError as err: LOGGER.error("Error while upgrading firmware: %s", err) - async def async_turn_off(self, **kwargs) -> None: + async def async_turn_off(self, **kwargs: dict[str, Any]) -> None: """Turn the valve off (closed).""" try: async with self._client: @@ -209,7 +213,7 @@ class ValveControllerSwitch(ValveControllerEntity, SwitchEntity): self._attr_is_on = False self.async_write_ha_state() - async def async_turn_on(self, **kwargs) -> None: + async def async_turn_on(self, **kwargs: dict[str, Any]) -> None: """Turn the valve on (open).""" try: async with self._client: diff --git a/homeassistant/components/guardian/util.py b/homeassistant/components/guardian/util.py index beaf71dea51..c4d0e0be4d7 100644 --- a/homeassistant/components/guardian/util.py +++ b/homeassistant/components/guardian/util.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio from collections.abc import Awaitable from datetime import timedelta -from typing import Callable +from typing import Any, Callable, Dict, cast from aioguardian import Client from aioguardian.errors import GuardianError @@ -42,11 +42,11 @@ class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): self._api_lock = api_lock self._client = client - async def _async_update_data(self) -> dict: + async def _async_update_data(self) -> dict[str, Any]: """Execute a "locked" API request against the valve controller.""" async with self._api_lock, self._client: try: resp = await self._api_coro() except GuardianError as err: raise UpdateFailed(err) from err - return resp["data"] + return cast(Dict[str, Any], resp["data"]) diff --git a/mypy.ini b/mypy.ini index 32fff8d5105..e0bdc372f50 100644 --- a/mypy.ini +++ b/mypy.ini @@ -484,6 +484,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.guardian.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.history.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1252,9 +1263,6 @@ ignore_errors = true [mypy-homeassistant.components.gtfs.*] ignore_errors = true -[mypy-homeassistant.components.guardian.*] -ignore_errors = true - [mypy-homeassistant.components.habitica.*] ignore_errors = true diff --git a/requirements_all.txt b/requirements_all.txt index 927a685ded4..fa3b4d2ba4c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -172,7 +172,7 @@ aioflo==0.4.1 aioftp==0.12.0 # homeassistant.components.guardian -aioguardian==1.0.4 +aioguardian==1.0.8 # homeassistant.components.harmony aioharmony==0.2.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 101e0bbe4b0..eb34d203904 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -109,7 +109,7 @@ aioesphomeapi==5.0.1 aioflo==0.4.1 # homeassistant.components.guardian -aioguardian==1.0.4 +aioguardian==1.0.8 # homeassistant.components.harmony aioharmony==0.2.7 diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 8dff2c8f89f..b1c74fceb45 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -61,7 +61,6 @@ IGNORED_MODULES: Final[list[str]] = [ "homeassistant.components.gree.*", "homeassistant.components.growatt_server.*", "homeassistant.components.gtfs.*", - "homeassistant.components.guardian.*", "homeassistant.components.habitica.*", "homeassistant.components.harmony.*", "homeassistant.components.hassio.*",