diff --git a/.coveragerc b/.coveragerc index 015d1c541e9..2f43fe3ab3e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -101,6 +101,7 @@ omit = homeassistant/components/azure_devops/__init__.py homeassistant/components/azure_devops/sensor.py homeassistant/components/azure_service_bus/* + homeassistant/components/awair/coordinator.py homeassistant/components/baf/__init__.py homeassistant/components/baf/climate.py homeassistant/components/baf/entity.py diff --git a/homeassistant/components/awair/__init__.py b/homeassistant/components/awair/__init__.py index 083c7d48b03..cb974707e93 100644 --- a/homeassistant/components/awair/__init__.py +++ b/homeassistant/components/awair/__init__.py @@ -1,29 +1,16 @@ """The awair component.""" from __future__ import annotations -from asyncio import gather, timeout -from dataclasses import dataclass -from datetime import timedelta - -from aiohttp import ClientSession -from python_awair import Awair, AwairLocal -from python_awair.air_data import AirData -from python_awair.devices import AwairBaseDevice, AwairLocalDevice -from python_awair.exceptions import AuthError, AwairError - from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, Platform +from homeassistant.const import CONF_HOST, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import ( - API_TIMEOUT, - DOMAIN, - LOGGER, - UPDATE_INTERVAL_CLOUD, - UPDATE_INTERVAL_LOCAL, +from .const import DOMAIN +from .coordinator import ( + AwairCloudDataUpdateCoordinator, + AwairDataUpdateCoordinator, + AwairLocalDataUpdateCoordinator, ) PLATFORMS = [Platform.SENSOR] @@ -70,93 +57,3 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> hass.data[DOMAIN].pop(config_entry.entry_id) return unload_ok - - -@dataclass -class AwairResult: - """Wrapper class to hold an awair device and set of air data.""" - - device: AwairBaseDevice - air_data: AirData - - -class AwairDataUpdateCoordinator(DataUpdateCoordinator[dict[str, AwairResult]]): - """Define a wrapper class to update Awair data.""" - - def __init__( - self, - hass: HomeAssistant, - config_entry: ConfigEntry, - update_interval: timedelta | None, - ) -> None: - """Set up the AwairDataUpdateCoordinator class.""" - self._config_entry = config_entry - self.title = config_entry.title - - super().__init__(hass, LOGGER, name=DOMAIN, update_interval=update_interval) - - async def _fetch_air_data(self, device: AwairBaseDevice) -> AwairResult: - """Fetch latest air quality data.""" - LOGGER.debug("Fetching data for %s", device.uuid) - air_data = await device.air_data_latest() - LOGGER.debug(air_data) - return AwairResult(device=device, air_data=air_data) - - -class AwairCloudDataUpdateCoordinator(AwairDataUpdateCoordinator): - """Define a wrapper class to update Awair data from Cloud API.""" - - def __init__( - self, hass: HomeAssistant, config_entry: ConfigEntry, session: ClientSession - ) -> None: - """Set up the AwairCloudDataUpdateCoordinator class.""" - access_token = config_entry.data[CONF_ACCESS_TOKEN] - self._awair = Awair(access_token=access_token, session=session) - - super().__init__(hass, config_entry, UPDATE_INTERVAL_CLOUD) - - async def _async_update_data(self) -> dict[str, AwairResult]: - """Update data via Awair client library.""" - async with timeout(API_TIMEOUT): - try: - LOGGER.debug("Fetching users and devices") - user = await self._awair.user() - devices = await user.devices() - results = await gather( - *(self._fetch_air_data(device) for device in devices) - ) - return {result.device.uuid: result for result in results} - except AuthError as err: - raise ConfigEntryAuthFailed from err - except Exception as err: - raise UpdateFailed(err) from err - - -class AwairLocalDataUpdateCoordinator(AwairDataUpdateCoordinator): - """Define a wrapper class to update Awair data from the local API.""" - - _device: AwairLocalDevice | None = None - - def __init__( - self, hass: HomeAssistant, config_entry: ConfigEntry, session: ClientSession - ) -> None: - """Set up the AwairLocalDataUpdateCoordinator class.""" - self._awair = AwairLocal( - session=session, device_addrs=[config_entry.data[CONF_HOST]] - ) - - super().__init__(hass, config_entry, UPDATE_INTERVAL_LOCAL) - - async def _async_update_data(self) -> dict[str, AwairResult]: - """Update data via Awair client library.""" - async with timeout(API_TIMEOUT): - try: - if self._device is None: - LOGGER.debug("Fetching devices") - devices = await self._awair.devices() - self._device = devices[0] - result = await self._fetch_air_data(self._device) - return {result.device.uuid: result} - except AwairError as err: - LOGGER.error("Unexpected API error: %s", err) - raise UpdateFailed(err) from err diff --git a/homeassistant/components/awair/coordinator.py b/homeassistant/components/awair/coordinator.py new file mode 100644 index 00000000000..b687a916a2d --- /dev/null +++ b/homeassistant/components/awair/coordinator.py @@ -0,0 +1,116 @@ +"""DataUpdateCoordinators for awair integration.""" +from __future__ import annotations + +from asyncio import gather, timeout +from dataclasses import dataclass +from datetime import timedelta + +from aiohttp import ClientSession +from python_awair import Awair, AwairLocal +from python_awair.air_data import AirData +from python_awair.devices import AwairBaseDevice, AwairLocalDevice +from python_awair.exceptions import AuthError, AwairError + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import ( + API_TIMEOUT, + DOMAIN, + LOGGER, + UPDATE_INTERVAL_CLOUD, + UPDATE_INTERVAL_LOCAL, +) + + +@dataclass +class AwairResult: + """Wrapper class to hold an awair device and set of air data.""" + + device: AwairBaseDevice + air_data: AirData + + +class AwairDataUpdateCoordinator(DataUpdateCoordinator[dict[str, AwairResult]]): + """Define a wrapper class to update Awair data.""" + + def __init__( + self, + hass: HomeAssistant, + config_entry: ConfigEntry, + update_interval: timedelta | None, + ) -> None: + """Set up the AwairDataUpdateCoordinator class.""" + self._config_entry = config_entry + self.title = config_entry.title + + super().__init__(hass, LOGGER, name=DOMAIN, update_interval=update_interval) + + async def _fetch_air_data(self, device: AwairBaseDevice) -> AwairResult: + """Fetch latest air quality data.""" + LOGGER.debug("Fetching data for %s", device.uuid) + air_data = await device.air_data_latest() + LOGGER.debug(air_data) + return AwairResult(device=device, air_data=air_data) + + +class AwairCloudDataUpdateCoordinator(AwairDataUpdateCoordinator): + """Define a wrapper class to update Awair data from Cloud API.""" + + def __init__( + self, hass: HomeAssistant, config_entry: ConfigEntry, session: ClientSession + ) -> None: + """Set up the AwairCloudDataUpdateCoordinator class.""" + access_token = config_entry.data[CONF_ACCESS_TOKEN] + self._awair = Awair(access_token=access_token, session=session) + + super().__init__(hass, config_entry, UPDATE_INTERVAL_CLOUD) + + async def _async_update_data(self) -> dict[str, AwairResult]: + """Update data via Awair client library.""" + async with timeout(API_TIMEOUT): + try: + LOGGER.debug("Fetching users and devices") + user = await self._awair.user() + devices = await user.devices() + results = await gather( + *(self._fetch_air_data(device) for device in devices) + ) + return {result.device.uuid: result for result in results} + except AuthError as err: + raise ConfigEntryAuthFailed from err + except Exception as err: + raise UpdateFailed(err) from err + + +class AwairLocalDataUpdateCoordinator(AwairDataUpdateCoordinator): + """Define a wrapper class to update Awair data from the local API.""" + + _device: AwairLocalDevice | None = None + + def __init__( + self, hass: HomeAssistant, config_entry: ConfigEntry, session: ClientSession + ) -> None: + """Set up the AwairLocalDataUpdateCoordinator class.""" + self._awair = AwairLocal( + session=session, device_addrs=[config_entry.data[CONF_HOST]] + ) + + super().__init__(hass, config_entry, UPDATE_INTERVAL_LOCAL) + + async def _async_update_data(self) -> dict[str, AwairResult]: + """Update data via Awair client library.""" + async with timeout(API_TIMEOUT): + try: + if self._device is None: + LOGGER.debug("Fetching devices") + devices = await self._awair.devices() + self._device = devices[0] + result = await self._fetch_air_data(self._device) + return {result.device.uuid: result} + except AwairError as err: + LOGGER.error("Unexpected API error: %s", err) + raise UpdateFailed(err) from err diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 27962167330..2a09a8d4e70 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -31,7 +31,6 @@ from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import AwairDataUpdateCoordinator, AwairResult from .const import ( API_CO2, API_DUST, @@ -46,6 +45,7 @@ from .const import ( ATTRIBUTION, DOMAIN, ) +from .coordinator import AwairDataUpdateCoordinator, AwairResult DUST_ALIASES = [API_PM25, API_PM10]