From b145352bbb59d2ffaec5c020231191841a5e2dcf Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 24 Aug 2023 13:44:43 +0200 Subject: [PATCH] Modernize meteo_france weather (#98022) * Modernize meteofrance weather * Remove options flow * Remove unused constant * Format code --------- Co-authored-by: Quentin POLLET --- .../components/meteo_france/config_flow.py | 39 ++------------ .../components/meteo_france/const.py | 1 - .../components/meteo_france/strings.json | 9 ---- .../components/meteo_france/weather.py | 52 ++++++++++++++----- .../meteo_france/test_config_flow.py | 42 +-------------- 5 files changed, 45 insertions(+), 98 deletions(-) diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index d05c63ef684..ade6bedd362 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -7,11 +7,11 @@ from meteofrance_api.client import MeteoFranceClient import voluptuous as vol from homeassistant import config_entries -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE +from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import callback -from .const import CONF_CITY, DOMAIN, FORECAST_MODE, FORECAST_MODE_DAILY +from .const import CONF_CITY, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -25,14 +25,6 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Init MeteoFranceFlowHandler.""" self.places = [] - @staticmethod - @callback - def async_get_options_flow( - config_entry: ConfigEntry, - ) -> MeteoFranceOptionsFlowHandler: - """Get the options flow for this handler.""" - return MeteoFranceOptionsFlowHandler(config_entry) - @callback def _show_setup_form(self, user_input=None, errors=None): """Show the setup form to the user.""" @@ -114,30 +106,5 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class MeteoFranceOptionsFlowHandler(config_entries.OptionsFlow): - """Handle a option flow.""" - - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: - """Initialize options flow.""" - self.config_entry = config_entry - - async def async_step_init(self, user_input=None): - """Handle options flow.""" - if user_input is not None: - return self.async_create_entry(title="", data=user_input) - - data_schema = vol.Schema( - { - vol.Optional( - CONF_MODE, - default=self.config_entry.options.get( - CONF_MODE, FORECAST_MODE_DAILY - ), - ): vol.In(FORECAST_MODE) - } - ) - return self.async_show_form(step_id="init", data_schema=data_schema) - - def _build_place_key(place) -> str: return f"{place};{place.latitude};{place.longitude}" diff --git a/homeassistant/components/meteo_france/const.py b/homeassistant/components/meteo_france/const.py index f1e6ae8d0eb..e950dfe1fa8 100644 --- a/homeassistant/components/meteo_france/const.py +++ b/homeassistant/components/meteo_france/const.py @@ -33,7 +33,6 @@ MANUFACTURER = "Météo-France" CONF_CITY = "city" FORECAST_MODE_HOURLY = "hourly" FORECAST_MODE_DAILY = "daily" -FORECAST_MODE = [FORECAST_MODE_HOURLY, FORECAST_MODE_DAILY] ATTR_NEXT_RAIN_1_HOUR_FORECAST = "1_hour_forecast" ATTR_NEXT_RAIN_DT_REF = "forecast_time_ref" diff --git a/homeassistant/components/meteo_france/strings.json b/homeassistant/components/meteo_france/strings.json index 944f2b32fab..7cb7d3efe53 100644 --- a/homeassistant/components/meteo_france/strings.json +++ b/homeassistant/components/meteo_france/strings.json @@ -21,14 +21,5 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_location%]", "unknown": "[%key:common::config_flow::error::unknown%]" } - }, - "options": { - "step": { - "init": { - "data": { - "mode": "Forecast mode" - } - } - } } } diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index 6459827b601..d081a6e729b 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -2,7 +2,7 @@ import logging import time -from meteofrance_api.model.forecast import Forecast +from meteofrance_api.model.forecast import Forecast as MeteoFranceForecast from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, @@ -13,7 +13,9 @@ from homeassistant.components.weather import ( ATTR_FORECAST_NATIVE_WIND_SPEED, ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, + Forecast, WeatherEntity, + WeatherEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -23,7 +25,7 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import ( @@ -55,9 +57,9 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the Meteo-France weather platform.""" - coordinator: DataUpdateCoordinator[Forecast] = hass.data[DOMAIN][entry.entry_id][ - COORDINATOR_FORECAST - ] + coordinator: DataUpdateCoordinator[MeteoFranceForecast] = hass.data[DOMAIN][ + entry.entry_id + ][COORDINATOR_FORECAST] async_add_entities( [ @@ -76,7 +78,7 @@ async def async_setup_entry( class MeteoFranceWeather( - CoordinatorEntity[DataUpdateCoordinator[Forecast]], WeatherEntity + CoordinatorEntity[DataUpdateCoordinator[MeteoFranceForecast]], WeatherEntity ): """Representation of a weather condition.""" @@ -85,14 +87,28 @@ class MeteoFranceWeather( _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS _attr_native_pressure_unit = UnitOfPressure.HPA _attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND + _attr_supported_features = ( + WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY + ) - def __init__(self, coordinator: DataUpdateCoordinator[Forecast], mode: str) -> None: + def __init__( + self, coordinator: DataUpdateCoordinator[MeteoFranceForecast], mode: str + ) -> None: """Initialise the platform with a data instance and station name.""" super().__init__(coordinator) self._city_name = self.coordinator.data.position["name"] self._mode = mode self._unique_id = f"{self.coordinator.data.position['lat']},{self.coordinator.data.position['lon']}" + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + super()._handle_coordinator_update() + assert self.platform.config_entry + self.platform.config_entry.async_create_task( + self.hass, self.async_update_listeners(("daily", "hourly")) + ) + @property def unique_id(self): """Return the unique id of the sensor.""" @@ -149,12 +165,11 @@ class MeteoFranceWeather( if wind_bearing != -1: return wind_bearing - @property - def forecast(self): + def _forecast(self, mode: str) -> list[Forecast]: """Return the forecast.""" - forecast_data = [] + forecast_data: list[Forecast] = [] - if self._mode == FORECAST_MODE_HOURLY: + if mode == FORECAST_MODE_HOURLY: today = time.time() for forecast in self.coordinator.data.forecast: # Can have data in the past @@ -186,7 +201,7 @@ class MeteoFranceWeather( { ATTR_FORECAST_TIME: self.coordinator.data.timestamp_to_locale_time( forecast["dt"] - ), + ).isoformat(), ATTR_FORECAST_CONDITION: format_condition( forecast["weather12H"]["desc"] ), @@ -199,3 +214,16 @@ class MeteoFranceWeather( } ) return forecast_data + + @property + def forecast(self) -> list[Forecast]: + """Return the forecast array.""" + return self._forecast(self._mode) + + async def async_forecast_daily(self) -> list[Forecast]: + """Return the daily forecast in native units.""" + return self._forecast(FORECAST_MODE_DAILY) + + async def async_forecast_hourly(self) -> list[Forecast]: + """Return the hourly forecast in native units.""" + return self._forecast(FORECAST_MODE_HOURLY) diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index e405d74ad53..80155d3311a 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -5,14 +5,9 @@ from meteofrance_api.model import Place import pytest from homeassistant import data_entry_flow -from homeassistant.components.meteo_france.const import ( - CONF_CITY, - DOMAIN, - FORECAST_MODE_DAILY, - FORECAST_MODE_HOURLY, -) +from homeassistant.components.meteo_france.const import CONF_CITY, DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @@ -212,36 +207,3 @@ async def test_abort_if_already_setup(hass: HomeAssistant, client_single) -> Non ) assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "already_configured" - - -async def test_options_flow(hass: HomeAssistant) -> None: - """Test config flow options.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data={CONF_LATITUDE: CITY_1_LAT, CONF_LONGITUDE: CITY_1_LON}, - unique_id=f"{CITY_1_LAT}, {CITY_1_LON}", - ) - config_entry.add_to_hass(hass) - - assert config_entry.options == {} - - result = await hass.config_entries.options.async_init(config_entry.entry_id) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "init" - - # Default - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={}, - ) - assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - assert config_entry.options[CONF_MODE] == FORECAST_MODE_DAILY - - # Manual - result = await hass.config_entries.options.async_init(config_entry.entry_id) - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_MODE: FORECAST_MODE_HOURLY}, - ) - assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - assert config_entry.options[CONF_MODE] == FORECAST_MODE_HOURLY