From e27d3c952396b52c5afe714f8379df2bf065d052 Mon Sep 17 00:00:00 2001 From: Renat Sibgatulin Date: Tue, 28 Mar 2023 06:18:47 +0000 Subject: [PATCH] Improve airq handling of DeviceInfo (#90232) * Reduce data sharing between ConfigFlow and DataUpdateCoordinator Instead of fetching device information from the device once in `ConfigFlow` and then piping it through in `ConfigEntry.data`, only use as much as needed in `ConfigFlow.async_step_user`, then fetch again in `AirQCoordinator._async_update_data` if a key is missing. Additionally, factor `AirQCoordinator` out into a sumbodule. Add a simple test for `AirQCoordinator.device_info` update. Positive side effect: `AirQCoordinator.device_info` is updated explicitly, instead of dumping the entire content of (a fully compatible) `TypedDict`, retrieved from `aioairq`. * Remove tests ill-suited to this PR `test_config_flow.test_duplicate_error` slipped through by mistake, while `test_coordinator.test_fetch_device_info_on_first_update` may need a more thoroughly suite of accompanying tests * Ignore airq/coordinator.py ...newly separated from airq/__init__.py, that's already in this list * Reorder files alphabetically --- .coveragerc | 1 + homeassistant/components/airq/__init__.py | 48 +-------------- homeassistant/components/airq/config_flow.py | 5 +- homeassistant/components/airq/coordinator.py | 61 ++++++++++++++++++++ tests/components/airq/test_config_flow.py | 5 +- 5 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 homeassistant/components/airq/coordinator.py diff --git a/.coveragerc b/.coveragerc index da7cc42ba15..520b87b08b9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -36,6 +36,7 @@ omit = homeassistant/components/airnow/__init__.py homeassistant/components/airnow/sensor.py homeassistant/components/airq/__init__.py + homeassistant/components/airq/coordinator.py homeassistant/components/airq/sensor.py homeassistant/components/airthings/__init__.py homeassistant/components/airthings/sensor.py diff --git a/homeassistant/components/airq/__init__.py b/homeassistant/components/airq/__init__.py index 4bc64e1e825..06d7ba30749 100644 --- a/homeassistant/components/airq/__init__.py +++ b/homeassistant/components/airq/__init__.py @@ -1,58 +1,16 @@ """The air-Q integration.""" from __future__ import annotations -from datetime import timedelta -import logging - -from aioairq import AirQ - from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, Platform +from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN, MANUFACTURER, TARGET_ROUTE, UPDATE_INTERVAL - -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN +from .coordinator import AirQCoordinator PLATFORMS: list[Platform] = [Platform.SENSOR] -class AirQCoordinator(DataUpdateCoordinator): - """Coordinator is responsible for querying the device at a specified route.""" - - def __init__( - self, - hass: HomeAssistant, - entry: ConfigEntry, - ) -> None: - """Initialise a custom coordinator.""" - super().__init__( - hass, - _LOGGER, - name=DOMAIN, - update_interval=timedelta(seconds=UPDATE_INTERVAL), - ) - session = async_get_clientsession(hass) - self.airq = AirQ( - entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], session - ) - self.device_id = entry.unique_id - assert self.device_id is not None - self.device_info = DeviceInfo( - manufacturer=MANUFACTURER, - identifiers={(DOMAIN, self.device_id)}, - ) - self.device_info.update(entry.data["device_info"]) - - async def _async_update_data(self) -> dict: - """Fetch the data from the device.""" - data = await self.airq.get(TARGET_ROUTE) - return self.airq.drop_uncertainties_from_data(data) - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up air-Q from a config entry.""" diff --git a/homeassistant/components/airq/config_flow.py b/homeassistant/components/airq/config_flow.py index 90a6b9e0555..41eda912e98 100644 --- a/homeassistant/components/airq/config_flow.py +++ b/homeassistant/components/airq/config_flow.py @@ -74,12 +74,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) device_info = await airq.fetch_device_info() - await self.async_set_unique_id(device_info.pop("id")) + await self.async_set_unique_id(device_info["id"]) self._abort_if_unique_id_configured() return self.async_create_entry( - title=device_info["name"], - data=user_input | {"device_info": device_info}, + title=device_info["name"], data=user_input ) return self.async_show_form( diff --git a/homeassistant/components/airq/coordinator.py b/homeassistant/components/airq/coordinator.py new file mode 100644 index 00000000000..78e9580c631 --- /dev/null +++ b/homeassistant/components/airq/coordinator.py @@ -0,0 +1,61 @@ +"""The air-Q integration.""" +from __future__ import annotations + +from datetime import timedelta +import logging + +from aioairq import AirQ + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN, MANUFACTURER, TARGET_ROUTE, UPDATE_INTERVAL + +_LOGGER = logging.getLogger(__name__) + + +class AirQCoordinator(DataUpdateCoordinator): + """Coordinator is responsible for querying the device at a specified route.""" + + def __init__( + self, + hass: HomeAssistant, + entry: ConfigEntry, + ) -> None: + """Initialise a custom coordinator.""" + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=timedelta(seconds=UPDATE_INTERVAL), + ) + session = async_get_clientsession(hass) + self.airq = AirQ( + entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], session + ) + self.device_id = entry.unique_id + assert self.device_id is not None + self.device_info = DeviceInfo( + manufacturer=MANUFACTURER, + identifiers={(DOMAIN, self.device_id)}, + ) + + async def _async_update_data(self) -> dict: + """Fetch the data from the device.""" + if "name" not in self.device_info: + info = await self.airq.fetch_device_info() + self.device_info.update( + DeviceInfo( + name=info["name"], + model=info["model"], + sw_version=info["sw_version"], + hw_version=info["hw_version"], + ) + ) + + data = await self.airq.get(TARGET_ROUTE) + return self.airq.drop_uncertainties_from_data(data) diff --git a/tests/components/airq/test_config_flow.py b/tests/components/airq/test_config_flow.py index 52bd5cd37fd..af71dc813e2 100644 --- a/tests/components/airq/test_config_flow.py +++ b/tests/components/airq/test_config_flow.py @@ -24,9 +24,6 @@ TEST_DEVICE_INFO = DeviceInfo( sw_version="sw", hw_version="hw", ) -TEST_DATA_OUT = TEST_USER_DATA | { - "device_info": {k: v for k, v in TEST_DEVICE_INFO.items() if k != "id"} -} async def test_form(hass: HomeAssistant) -> None: @@ -48,7 +45,7 @@ async def test_form(hass: HomeAssistant) -> None: assert result2["type"] == FlowResultType.CREATE_ENTRY assert result2["title"] == TEST_DEVICE_INFO["name"] - assert result2["data"] == TEST_DATA_OUT + assert result2["data"] == TEST_USER_DATA async def test_form_invalid_auth(hass: HomeAssistant) -> None: