diff --git a/homeassistant/components/aemet/weather.py b/homeassistant/components/aemet/weather.py index f9b0f7ef6ca..6affc39c7a8 100644 --- a/homeassistant/components/aemet/weather.py +++ b/homeassistant/components/aemet/weather.py @@ -11,8 +11,8 @@ from homeassistant.components.weather import ( ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, DOMAIN as WEATHER_DOMAIN, + CoordinatorWeatherEntity, Forecast, - WeatherEntity, WeatherEntityFeature, ) from homeassistant.config_entries import ConfigEntry @@ -22,10 +22,9 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( ATTR_API_CONDITION, @@ -111,7 +110,7 @@ async def async_setup_entry( async_add_entities(entities, False) -class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity): +class AemetWeather(CoordinatorWeatherEntity[WeatherUpdateCoordinator]): """Implementation of an AEMET OpenData sensor.""" _attr_attribution = ATTRIBUTION @@ -139,15 +138,6 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity): self._attr_name = name self._attr_unique_id = unique_id - @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 condition(self): """Return the current condition.""" diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index bdc300dc9a3..67cb2df5473 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -22,8 +22,8 @@ from homeassistant.components.weather import ( ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_TIME, DOMAIN as WEATHER_DOMAIN, + CoordinatorWeatherEntity, Forecast, - WeatherEntity, WeatherEntityFeature, ) from homeassistant.config_entries import ConfigEntry @@ -33,10 +33,9 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import dt as dt_util from . import device_info @@ -87,7 +86,7 @@ def _calculate_unique_id(config_entry_unique_id: str | None, hourly: bool) -> st return f"{config_entry_unique_id}{'-hourly' if hourly else '-daily'}" -class ECWeather(CoordinatorEntity, WeatherEntity): +class ECWeather(CoordinatorWeatherEntity): """Representation of a weather condition.""" _attr_has_entity_name = True @@ -112,15 +111,6 @@ class ECWeather(CoordinatorEntity, WeatherEntity): self._hourly = hourly self._attr_device_info = device_info(coordinator.config_entry) - @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 native_temperature(self): """Return the temperature.""" diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index e7aea21875a..c697befd01f 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -16,8 +16,8 @@ from homeassistant.components.weather import ( ATTR_WEATHER_WIND_GUST_SPEED, ATTR_WEATHER_WIND_SPEED, DOMAIN as WEATHER_DOMAIN, + CoordinatorWeatherEntity, Forecast, - WeatherEntity, WeatherEntityFeature, ) from homeassistant.config_entries import ConfigEntry @@ -30,11 +30,10 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.unit_system import METRIC_SYSTEM from . import MetDataUpdateCoordinator @@ -92,7 +91,7 @@ def format_condition(condition: str) -> str: return condition -class MetWeather(CoordinatorEntity[MetDataUpdateCoordinator], WeatherEntity): +class MetWeather(CoordinatorWeatherEntity[MetDataUpdateCoordinator]): """Implementation of a Met.no weather condition.""" _attr_attribution = ( @@ -148,15 +147,6 @@ class MetWeather(CoordinatorEntity[MetDataUpdateCoordinator], WeatherEntity): """Return if the entity should be enabled when first added to the entity registry.""" return not self._hourly - @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 condition(self) -> str | None: """Return the current condition.""" diff --git a/homeassistant/components/met_eireann/weather.py b/homeassistant/components/met_eireann/weather.py index c40c89892c9..a69c1f24c08 100644 --- a/homeassistant/components/met_eireann/weather.py +++ b/homeassistant/components/met_eireann/weather.py @@ -7,8 +7,8 @@ from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, ATTR_FORECAST_TIME, DOMAIN as WEATHER_DOMAIN, + CoordinatorWeatherEntity, Forecast, - WeatherEntity, WeatherEntityFeature, ) from homeassistant.config_entries import ConfigEntry @@ -21,14 +21,11 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, -) +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import dt as dt_util from . import MetEireannWeatherData @@ -78,7 +75,7 @@ def _calculate_unique_id(config: MappingProxyType[str, Any], hourly: bool) -> st class MetEireannWeather( - CoordinatorEntity[DataUpdateCoordinator[MetEireannWeatherData]], WeatherEntity + CoordinatorWeatherEntity[DataUpdateCoordinator[MetEireannWeatherData]] ): """Implementation of a Met Éireann weather condition.""" @@ -98,15 +95,6 @@ class MetEireannWeather( self._config = config self._hourly = hourly - @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 name(self): """Return the name of the sensor.""" diff --git a/homeassistant/components/tomorrowio/weather.py b/homeassistant/components/tomorrowio/weather.py index ec77a2c8040..f88887e64dd 100644 --- a/homeassistant/components/tomorrowio/weather.py +++ b/homeassistant/components/tomorrowio/weather.py @@ -17,8 +17,8 @@ from homeassistant.components.weather import ( ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, DOMAIN as WEATHER_DOMAIN, + CoordinatorWeatherEntity, Forecast, - WeatherEntity, WeatherEntityFeature, ) from homeassistant.config_entries import ConfigEntry @@ -31,7 +31,7 @@ from homeassistant.const import ( UnitOfSpeed, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.sun import is_up @@ -93,7 +93,7 @@ def _calculate_unique_id(config_entry_unique_id: str | None, forecast_type: str) return f"{config_entry_unique_id}_{forecast_type}" -class TomorrowioWeatherEntity(TomorrowioEntity, WeatherEntity): +class TomorrowioWeatherEntity(TomorrowioEntity, CoordinatorWeatherEntity): """Entity that talks to Tomorrow.io v4 API to retrieve weather data.""" _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS @@ -123,15 +123,6 @@ class TomorrowioWeatherEntity(TomorrowioEntity, WeatherEntity): config_entry.unique_id, forecast_type ) - @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")) - ) - def _forecast_dict( self, forecast_dt: datetime, diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 74671f0c1df..8652f947f7c 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -8,7 +8,7 @@ from dataclasses import dataclass from datetime import timedelta import inspect import logging -from typing import Any, Final, Literal, Required, TypedDict, final +from typing import Any, Final, Literal, Required, TypedDict, TypeVar, final import voluptuous as vol @@ -37,6 +37,10 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) from homeassistant.util.json import JsonValueType from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM @@ -117,6 +121,10 @@ ROUNDING_PRECISION = 2 SERVICE_GET_FORECAST: Final = "get_forecast" +_DataUpdateCoordinatorT = TypeVar( + "_DataUpdateCoordinatorT", bound="DataUpdateCoordinator[Any]" +) + # mypy: disallow-any-generics @@ -1155,3 +1163,18 @@ async def async_get_forecast_service( return { "forecast": converted_forecast_list, } + + +class CoordinatorWeatherEntity( + CoordinatorEntity[_DataUpdateCoordinatorT], WeatherEntity +): + """A class for weather entities using a single DataUpdateCoordinator.""" + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + super()._handle_coordinator_update() + assert self.coordinator.config_entry + self.coordinator.config_entry.async_create_task( + self.hass, self.async_update_listeners(None) + )