diff --git a/homeassistant/components/open_meteo/__init__.py b/homeassistant/components/open_meteo/__init__.py index 6deb63904ff..fa49d610358 100644 --- a/homeassistant/components/open_meteo/__init__.py +++ b/homeassistant/components/open_meteo/__init__.py @@ -2,71 +2,20 @@ from __future__ import annotations -from open_meteo import ( - DailyParameters, - Forecast, - HourlyParameters, - OpenMeteo, - OpenMeteoError, - PrecipitationUnit, - TemperatureUnit, - WindSpeedUnit, -) - from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ZONE, Platform +from homeassistant.const import Platform 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 +from .const import DOMAIN +from .coordinator import OpenMeteoDataUpdateCoordinator PLATFORMS = [Platform.WEATHER] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Open-Meteo from a config entry.""" - session = async_get_clientsession(hass) - open_meteo = OpenMeteo(session=session) - async def async_update_forecast() -> Forecast: - if (zone := hass.states.get(entry.data[CONF_ZONE])) is None: - raise UpdateFailed(f"Zone '{entry.data[CONF_ZONE]}' not found") - - try: - return await open_meteo.forecast( - latitude=zone.attributes[ATTR_LATITUDE], - longitude=zone.attributes[ATTR_LONGITUDE], - current_weather=True, - daily=[ - DailyParameters.PRECIPITATION_SUM, - DailyParameters.TEMPERATURE_2M_MAX, - DailyParameters.TEMPERATURE_2M_MIN, - DailyParameters.WEATHER_CODE, - DailyParameters.WIND_DIRECTION_10M_DOMINANT, - DailyParameters.WIND_SPEED_10M_MAX, - ], - hourly=[ - HourlyParameters.PRECIPITATION, - HourlyParameters.TEMPERATURE_2M, - HourlyParameters.WEATHER_CODE, - ], - precipitation_unit=PrecipitationUnit.MILLIMETERS, - temperature_unit=TemperatureUnit.CELSIUS, - timezone="UTC", - wind_speed_unit=WindSpeedUnit.KILOMETERS_PER_HOUR, - ) - except OpenMeteoError as err: - raise UpdateFailed("Open-Meteo API communication error") from err - - coordinator: DataUpdateCoordinator[Forecast] = DataUpdateCoordinator( - hass, - LOGGER, - config_entry=entry, - name=f"{DOMAIN}_{entry.data[CONF_ZONE]}", - update_interval=SCAN_INTERVAL, - update_method=async_update_forecast, - ) + coordinator = OpenMeteoDataUpdateCoordinator(hass, entry) await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator diff --git a/homeassistant/components/open_meteo/coordinator.py b/homeassistant/components/open_meteo/coordinator.py new file mode 100644 index 00000000000..625ee991542 --- /dev/null +++ b/homeassistant/components/open_meteo/coordinator.py @@ -0,0 +1,71 @@ +"""DataUpdateCoordinator for the Open-Meteo integration.""" + +from __future__ import annotations + +from open_meteo import ( + DailyParameters, + Forecast, + HourlyParameters, + OpenMeteo, + OpenMeteoError, + PrecipitationUnit, + TemperatureUnit, + WindSpeedUnit, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ZONE +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 + + +class OpenMeteoDataUpdateCoordinator(DataUpdateCoordinator[Forecast]): + """A Open-Meteo Data Update Coordinator.""" + + config_entry: ConfigEntry + + def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Initialize the Open-Meteo coordinator.""" + super().__init__( + hass, + LOGGER, + config_entry=config_entry, + name=f"{DOMAIN}_{config_entry.data[CONF_ZONE]}", + update_interval=SCAN_INTERVAL, + ) + session = async_get_clientsession(hass) + self.open_meteo = OpenMeteo(session=session) + + async def _async_update_data(self) -> Forecast: + """Fetch data from Sensibo.""" + if (zone := self.hass.states.get(self.config_entry.data[CONF_ZONE])) is None: + raise UpdateFailed(f"Zone '{self.config_entry.data[CONF_ZONE]}' not found") + + try: + return await self.open_meteo.forecast( + latitude=zone.attributes[ATTR_LATITUDE], + longitude=zone.attributes[ATTR_LONGITUDE], + current_weather=True, + daily=[ + DailyParameters.PRECIPITATION_SUM, + DailyParameters.TEMPERATURE_2M_MAX, + DailyParameters.TEMPERATURE_2M_MIN, + DailyParameters.WEATHER_CODE, + DailyParameters.WIND_DIRECTION_10M_DOMINANT, + DailyParameters.WIND_SPEED_10M_MAX, + ], + hourly=[ + HourlyParameters.PRECIPITATION, + HourlyParameters.TEMPERATURE_2M, + HourlyParameters.WEATHER_CODE, + ], + precipitation_unit=PrecipitationUnit.MILLIMETERS, + temperature_unit=TemperatureUnit.CELSIUS, + timezone="UTC", + wind_speed_unit=WindSpeedUnit.KILOMETERS_PER_HOUR, + ) + except OpenMeteoError as err: + raise UpdateFailed("Open-Meteo API communication error") from err diff --git a/tests/components/open_meteo/conftest.py b/tests/components/open_meteo/conftest.py index 22138846915..66670782265 100644 --- a/tests/components/open_meteo/conftest.py +++ b/tests/components/open_meteo/conftest.py @@ -43,7 +43,7 @@ def mock_open_meteo(request: pytest.FixtureRequest) -> Generator[MagicMock]: forecast = Forecast.from_json(load_fixture(fixture, DOMAIN)) with patch( - "homeassistant.components.open_meteo.OpenMeteo", autospec=True + "homeassistant.components.open_meteo.coordinator.OpenMeteo", autospec=True ) as open_meteo_mock: open_meteo = open_meteo_mock.return_value open_meteo.forecast.return_value = forecast diff --git a/tests/components/open_meteo/test_init.py b/tests/components/open_meteo/test_init.py index 19efcbec35c..39e3666ef7f 100644 --- a/tests/components/open_meteo/test_init.py +++ b/tests/components/open_meteo/test_init.py @@ -33,7 +33,7 @@ async def test_load_unload_config_entry( @patch( - "homeassistant.components.open_meteo.OpenMeteo.forecast", + "homeassistant.components.open_meteo.coordinator.OpenMeteo.forecast", side_effect=OpenMeteoConnectionError, ) async def test_config_entry_not_ready(