diff --git a/custom_components/irm_kmi/const.py b/custom_components/irm_kmi/const.py index 31c2bdc..72727ef 100644 --- a/custom_components/irm_kmi/const.py +++ b/custom_components/irm_kmi/const.py @@ -48,11 +48,13 @@ STYLE_TO_PARAM_MAP: Final = { CONF_USE_DEPRECATED_FORECAST: Final = 'use_deprecated_forecast_attribute' OPTION_DEPRECATED_FORECAST_NOT_USED: Final = 'do_not_use_deprecated_forecast' OPTION_DEPRECATED_FORECAST_DAILY: Final = 'daily_in_deprecated_forecast' -OPTION_DEPRECATED_FORECAST_HOURLY: Final = 'hourly_in_use_deprecated_forecast' +OPTION_DEPRECATED_FORECAST_TWICE_DAILY: Final = 'twice_daily_in_deprecated_forecast' +OPTION_DEPRECATED_FORECAST_HOURLY: Final = 'hourly_in_deprecated_forecast' CONF_USE_DEPRECATED_FORECAST_OPTIONS: Final = [ OPTION_DEPRECATED_FORECAST_NOT_USED, OPTION_DEPRECATED_FORECAST_DAILY, + OPTION_DEPRECATED_FORECAST_TWICE_DAILY, OPTION_DEPRECATED_FORECAST_HOURLY ] diff --git a/custom_components/irm_kmi/translations/en.json b/custom_components/irm_kmi/translations/en.json index ebd4add..b3c488a 100644 --- a/custom_components/irm_kmi/translations/en.json +++ b/custom_components/irm_kmi/translations/en.json @@ -34,7 +34,8 @@ "options": { "do_not_use_deprecated_forecast": "Do not use (recommended)", "daily_in_deprecated_forecast": "Use for daily forecast", - "hourly_in_use_deprecated_forecast": "Use for hourly forecast" + "twice_daily_in_deprecated_forecast": "Use for daily forecast", + "hourly_in_deprecated_forecast": "Use for hourly forecast" } }, "repair_solution": { diff --git a/custom_components/irm_kmi/translations/fr.json b/custom_components/irm_kmi/translations/fr.json index 547ddb2..617b50f 100644 --- a/custom_components/irm_kmi/translations/fr.json +++ b/custom_components/irm_kmi/translations/fr.json @@ -34,7 +34,8 @@ "options": { "do_not_use_deprecated_forecast": "Ne pas utiliser (recommandé)", "daily_in_deprecated_forecast": "Utiliser pour les prévisions quotidiennes", - "hourly_in_use_deprecated_forecast": "Utiliser pour les prévisions horaires" + "twice_daily_in_deprecated_forecast": "Utiliser pour les prévisions biquotidiennes", + "hourly_in_deprecated_forecast": "Utiliser pour les prévisions horaires" } }, "repair_solution": { diff --git a/custom_components/irm_kmi/translations/nl.json b/custom_components/irm_kmi/translations/nl.json index 620e7a8..d5ada4c 100644 --- a/custom_components/irm_kmi/translations/nl.json +++ b/custom_components/irm_kmi/translations/nl.json @@ -34,7 +34,8 @@ "options": { "do_not_use_deprecated_forecast": "Niet gebruiken (aanbevolen)", "daily_in_deprecated_forecast": "Gebruik voor dagelijkse voorspellingen", - "hourly_in_use_deprecated_forecast": "Gebruik voor uurlijkse voorspellingen" + "twice_daily_in_deprecated_forecast": "Gebruik voor tweemaal daags voorspellingen", + "hourly_in_deprecated_forecast": "Gebruik voor uurlijkse voorspellingen" } }, "repair_solution": { diff --git a/custom_components/irm_kmi/weather.py b/custom_components/irm_kmi/weather.py index 02172ff..8b57ef1 100644 --- a/custom_components/irm_kmi/weather.py +++ b/custom_components/irm_kmi/weather.py @@ -1,4 +1,5 @@ """Support for IRM KMI weather.""" +import copy import logging from typing import List @@ -14,7 +15,8 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import CONF_USE_DEPRECATED_FORECAST, DOMAIN from .const import (OPTION_DEPRECATED_FORECAST_DAILY, OPTION_DEPRECATED_FORECAST_HOURLY, - OPTION_DEPRECATED_FORECAST_NOT_USED) + OPTION_DEPRECATED_FORECAST_NOT_USED, + OPTION_DEPRECATED_FORECAST_TWICE_DAILY) from .coordinator import IrmKmiCoordinator from .utils import get_config_value @@ -42,6 +44,11 @@ class IrmKmiWeather(CoordinatorEntity, WeatherEntity): self._deprecated_forecast_as = get_config_value(entry, CONF_USE_DEPRECATED_FORECAST) + if self._deprecated_forecast_as != OPTION_DEPRECATED_FORECAST_NOT_USED: + _LOGGER.warning(f"You are using the forecast attribute for {entry.title} weather. Home Assistant deleted " + f"that attribute in 2024.4. Consider using the service weather.get_forecasts instead " + f"as the attribute will be delete from this integration in a future release.") + @property def supported_features(self) -> WeatherEntityFeature: features = WeatherEntityFeature(0) @@ -98,17 +105,6 @@ class IrmKmiWeather(CoordinatorEntity, WeatherEntity): def uv_index(self) -> float | None: return self.coordinator.data.get('current_weather', {}).get('uv_index') - @property - def forecast(self) -> list[Forecast] | None: - """This attribute is deprecated by Home Assistant by still implemented for compatibility - with older components. Newer components should use the service weather.get_forecasts instead.""" - if self._deprecated_forecast_as == OPTION_DEPRECATED_FORECAST_NOT_USED: - return None - elif self._deprecated_forecast_as == OPTION_DEPRECATED_FORECAST_HOURLY: - return self.coordinator.data.get('hourly_forecast') - elif self._deprecated_forecast_as == OPTION_DEPRECATED_FORECAST_DAILY: - return self.daily_forecast() - async def async_forecast_twice_daily(self) -> List[Forecast] | None: return self.coordinator.data.get('daily_forecast') @@ -137,3 +133,26 @@ class IrmKmiWeather(CoordinatorEntity, WeatherEntity): (data[0]['native_temperature'], data[0]['native_templow']) return [f for f in data if f.get('is_daytime')] + + @property + def extra_state_attributes(self) -> dict: + """Here to keep the DEPRECATED forecast attribute. + This attribute is deprecated by Home Assistant by still implemented for compatibility + with older components. Newer components should use the service weather.get_forecasts instead. + """ + data: List[Forecast] = list() + if self._deprecated_forecast_as == OPTION_DEPRECATED_FORECAST_NOT_USED: + return {} + elif self._deprecated_forecast_as == OPTION_DEPRECATED_FORECAST_HOURLY: + data = self.coordinator.data.get('hourly_forecast') + elif self._deprecated_forecast_as == OPTION_DEPRECATED_FORECAST_DAILY: + data = self.daily_forecast() + elif self._deprecated_forecast_as == OPTION_DEPRECATED_FORECAST_TWICE_DAILY: + data = self.coordinator.data.get('daily_forecast') + + for forecast in data: + for k in list(forecast.keys()): + if k.startswith('native_'): + forecast[k[7:]] = forecast[k] + + return {'forecast': data} diff --git a/tests/conftest.py b/tests/conftest.py index 98b27a7..3efeef0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,7 @@ from custom_components.irm_kmi.api import (IrmKmiApiError, IrmKmiApiParametersError) from custom_components.irm_kmi.const import ( CONF_DARK_MODE, CONF_STYLE, CONF_USE_DEPRECATED_FORECAST, DOMAIN, - OPTION_DEPRECATED_FORECAST_NOT_USED, OPTION_STYLE_STD) + OPTION_DEPRECATED_FORECAST_NOT_USED, OPTION_STYLE_STD, OPTION_DEPRECATED_FORECAST_TWICE_DAILY) def get_api_data(fixture: str) -> dict: @@ -56,6 +56,20 @@ def mock_config_entry() -> MockConfigEntry: ) +@pytest.fixture +def mock_config_entry_with_deprecated() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="Home", + domain=DOMAIN, + data={CONF_ZONE: "zone.home", + CONF_STYLE: OPTION_STYLE_STD, + CONF_DARK_MODE: True, + CONF_USE_DEPRECATED_FORECAST: OPTION_DEPRECATED_FORECAST_TWICE_DAILY}, + unique_id="zone.home", + ) + + @pytest.fixture def mock_setup_entry() -> Generator[None, None, None]: """Mock setting up a config entry.""" @@ -196,6 +210,23 @@ def mock_image_and_high_temp_irm_kmi_api(request: pytest.FixtureRequest) -> Gene yield irm_kmi +@pytest.fixture() +def mock_image_and_simple_forecast_irm_kmi_api(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None]: + """Return a mocked IrmKmi api client.""" + fixture: str = "forecast.json" + + forecast = json.loads(load_fixture(fixture)) + + with patch( + "custom_components.irm_kmi.coordinator.IrmKmiApiClient", autospec=True + ) as irm_kmi_api_mock: + irm_kmi = irm_kmi_api_mock.return_value + irm_kmi.get_image.side_effect = patched + irm_kmi.get_svg.return_value = "" + irm_kmi.get_forecasts_coord.return_value = forecast + yield irm_kmi + + @pytest.fixture() def mock_svg_pollen(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None]: """Return a mocked IrmKmi api client.""" diff --git a/tests/test_weather.py b/tests/test_weather.py index 872996f..0ac3569 100644 --- a/tests/test_weather.py +++ b/tests/test_weather.py @@ -67,3 +67,27 @@ async def test_weather_higher_temp_at_night( for f in result: if f['native_temperature'] is not None and f['native_templow'] is not None: assert f['native_temperature'] >= f['native_templow'] + + +@freeze_time(datetime.fromisoformat("2023-12-26T18:30:00+01:00")) +async def test_forecast_attribute_same_as_service_call( + hass: HomeAssistant, + mock_image_and_simple_forecast_irm_kmi_api: AsyncMock, + mock_config_entry_with_deprecated: MockConfigEntry +) -> None: + hass.states.async_set( + "zone.home", + 0, + {"latitude": 50.738681639, "longitude": 4.054077148}, + ) + hass.config.config_dir = os.getcwd() + + coordinator = IrmKmiCoordinator(hass, mock_config_entry_with_deprecated) + await coordinator.async_config_entry_first_refresh() + + weather = IrmKmiWeather(coordinator, mock_config_entry_with_deprecated) + + result_service: List[Forecast] = await weather.async_forecast_twice_daily() + result_forecast: List[Forecast] = weather.extra_state_attributes['forecast'] + + assert result_service == result_forecast