From d6a9489212db045d59bbacbb3a0d93cb9829f7d6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 25 Nov 2024 21:46:29 +0100 Subject: [PATCH 1/2] Add data coordinator to Stookwijzer --- .../components/stookwijzer/__init__.py | 25 ++++++----- .../components/stookwijzer/coordinator.py | 44 +++++++++++++++++++ .../components/stookwijzer/diagnostics.py | 9 ++-- .../components/stookwijzer/sensor.py | 26 ++++------- .../components/stookwijzer/strings.json | 5 +++ tests/components/stookwijzer/conftest.py | 4 ++ 6 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 homeassistant/components/stookwijzer/coordinator.py diff --git a/homeassistant/components/stookwijzer/__init__.py b/homeassistant/components/stookwijzer/__init__.py index f121c8ab4bb232..ee525c5323a36f 100644 --- a/homeassistant/components/stookwijzer/__init__.py +++ b/homeassistant/components/stookwijzer/__init__.py @@ -4,36 +4,37 @@ from stookwijzer import Stookwijzer -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, LOGGER +from .coordinator import StookwijzerConfigEntry, StookwijzerCoordinator PLATFORMS = [Platform.SENSOR] -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: StookwijzerConfigEntry) -> bool: """Set up Stookwijzer from a config entry.""" - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = Stookwijzer( - async_get_clientsession(hass), - entry.data[CONF_LATITUDE], - entry.data[CONF_LONGITUDE], - ) + coordinator = StookwijzerCoordinator(hass, entry) + await coordinator.async_config_entry_first_refresh() + + entry.runtime_data = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_unload_entry( + hass: HomeAssistant, entry: StookwijzerConfigEntry +) -> bool: """Unload Stookwijzer config entry.""" - if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): - del hass.data[DOMAIN][entry.entry_id] - return unload_ok + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) -async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_migrate_entry( + hass: HomeAssistant, entry: StookwijzerConfigEntry +) -> bool: """Migrate old entry.""" LOGGER.debug("Migrating from version %s", entry.version) diff --git a/homeassistant/components/stookwijzer/coordinator.py b/homeassistant/components/stookwijzer/coordinator.py new file mode 100644 index 00000000000000..23092bed66e849 --- /dev/null +++ b/homeassistant/components/stookwijzer/coordinator.py @@ -0,0 +1,44 @@ +"""Class representing a Stookwijzer update coordinator.""" + +from datetime import timedelta + +from stookwijzer import Stookwijzer + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN, LOGGER + +SCAN_INTERVAL = timedelta(minutes=60) + +type StookwijzerConfigEntry = ConfigEntry[StookwijzerCoordinator] + + +class StookwijzerCoordinator(DataUpdateCoordinator[None]): + """Stookwijzer update coordinator.""" + + def __init__(self, hass: HomeAssistant, entry: StookwijzerConfigEntry) -> None: + """Initialize the coordinator.""" + super().__init__( + hass, + LOGGER, + name=DOMAIN, + update_interval=SCAN_INTERVAL, + ) + self.client = Stookwijzer( + async_get_clientsession(hass), + entry.data[CONF_LATITUDE], + entry.data[CONF_LONGITUDE], + ) + + async def _async_update_data(self) -> None: + """Fetch data from API endpoint.""" + await self.client.async_update() + if self.client.advice is None: + raise UpdateFailed( + translation_domain=DOMAIN, + translation_key="no_data_received", + ) diff --git a/homeassistant/components/stookwijzer/diagnostics.py b/homeassistant/components/stookwijzer/diagnostics.py index 59c28482541f97..c59a2e6175214b 100644 --- a/homeassistant/components/stookwijzer/diagnostics.py +++ b/homeassistant/components/stookwijzer/diagnostics.py @@ -4,19 +4,16 @@ from typing import Any -from stookwijzer import Stookwijzer - -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import DOMAIN +from .coordinator import StookwijzerConfigEntry async def async_get_config_entry_diagnostics( - hass: HomeAssistant, entry: ConfigEntry + hass: HomeAssistant, entry: StookwijzerConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - client: Stookwijzer = hass.data[DOMAIN][entry.entry_id] + client = entry.runtime_data.client return { "advice": client.advice, } diff --git a/homeassistant/components/stookwijzer/sensor.py b/homeassistant/components/stookwijzer/sensor.py index 8489da82c36b8e..b7d3f0ec8f4440 100644 --- a/homeassistant/components/stookwijzer/sensor.py +++ b/homeassistant/components/stookwijzer/sensor.py @@ -4,30 +4,28 @@ from datetime import timedelta -from stookwijzer import Stookwijzer - from homeassistant.components.sensor import SensorDeviceClass, SensorEntity -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN +from .coordinator import StookwijzerConfigEntry, StookwijzerCoordinator SCAN_INTERVAL = timedelta(minutes=60) async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: StookwijzerConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up Stookwijzer sensor from a config entry.""" - client = hass.data[DOMAIN][entry.entry_id] - async_add_entities([StookwijzerSensor(client, entry)], update_before_add=True) + async_add_entities([StookwijzerSensor(entry)]) -class StookwijzerSensor(SensorEntity): +class StookwijzerSensor(CoordinatorEntity[StookwijzerCoordinator], SensorEntity): """Defines a Stookwijzer binary sensor.""" _attr_attribution = "Data provided by atlasleefomgeving.nl" @@ -35,9 +33,10 @@ class StookwijzerSensor(SensorEntity): _attr_has_entity_name = True _attr_translation_key = "advice" - def __init__(self, client: Stookwijzer, entry: ConfigEntry) -> None: + def __init__(self, entry: StookwijzerConfigEntry) -> None: """Initialize a Stookwijzer device.""" - self._client = client + super().__init__(entry.runtime_data) + self._client = entry.runtime_data.client self._attr_options = ["code_yellow", "code_orange", "code_red"] self._attr_unique_id = entry.entry_id self._attr_device_info = DeviceInfo( @@ -47,15 +46,6 @@ def __init__(self, client: Stookwijzer, entry: ConfigEntry) -> None: configuration_url="https://www.atlasleefomgeving.nl/stookwijzer", ) - async def async_update(self) -> None: - """Update the data from the Stookwijzer handler.""" - await self._client.async_update() - - @property - def available(self) -> bool: - """Return if entity is available.""" - return self._client.advice is not None - @property def native_value(self) -> str | None: """Return the state of the device.""" diff --git a/homeassistant/components/stookwijzer/strings.json b/homeassistant/components/stookwijzer/strings.json index 253b103456721d..975b0dbfba3e4e 100644 --- a/homeassistant/components/stookwijzer/strings.json +++ b/homeassistant/components/stookwijzer/strings.json @@ -29,5 +29,10 @@ "description": "The Stookwijzer integration was unable to automatically migrate your location to a new format the updated integrations uses.\n\nMake sure you are connected to the internet and restart Home Assistant to try again.\n\nIf this doesn't resolve the error, remove and re-add the integration.", "title": "Migration of your location failed" } + }, + "exceptions": { + "no_data_received": { + "message": "No data received from Stookwijzer." + } } } diff --git a/tests/components/stookwijzer/conftest.py b/tests/components/stookwijzer/conftest.py index 974b6ef5643762..863becd720fbe6 100644 --- a/tests/components/stookwijzer/conftest.py +++ b/tests/components/stookwijzer/conftest.py @@ -61,6 +61,10 @@ def mock_stookwijzer() -> Generator[MagicMock]: "homeassistant.components.stookwijzer.Stookwijzer", autospec=True, ) as stookwijzer_mock, + patch( + "homeassistant.components.stookwijzer.coordinator.Stookwijzer", + new=stookwijzer_mock, + ), patch( "homeassistant.components.stookwijzer.config_flow.Stookwijzer", new=stookwijzer_mock, From 879f687e27b2fcd98ab2ae1764f0f6ffcf2d9ede Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 25 Nov 2024 21:53:03 +0100 Subject: [PATCH 2/2] Process review comment --- homeassistant/components/stookwijzer/sensor.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/stookwijzer/sensor.py b/homeassistant/components/stookwijzer/sensor.py index b7d3f0ec8f4440..25396639ecda33 100644 --- a/homeassistant/components/stookwijzer/sensor.py +++ b/homeassistant/components/stookwijzer/sensor.py @@ -2,8 +2,6 @@ from __future__ import annotations -from datetime import timedelta - from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo @@ -13,8 +11,6 @@ from .const import DOMAIN from .coordinator import StookwijzerConfigEntry, StookwijzerCoordinator -SCAN_INTERVAL = timedelta(minutes=60) - async def async_setup_entry( hass: HomeAssistant,