diff --git a/homeassistant/components/accuweather/weather.py b/homeassistant/components/accuweather/weather.py index 0dc4c7e270c..ee0ef69d666 100644 --- a/homeassistant/components/accuweather/weather.py +++ b/homeassistant/components/accuweather/weather.py @@ -13,6 +13,7 @@ from homeassistant.components.weather import ( ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, ATTR_FORECAST_WIND_SPEED, + Forecast, WeatherEntity, ) from homeassistant.config_entries import ConfigEntry @@ -156,12 +157,12 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity): return None @property - def forecast(self) -> list[dict[str, Any]] | None: + def forecast(self) -> list[Forecast] | None: """Return the forecast array.""" if not self.coordinator.forecast: return None # remap keys from library to keys understood by the weather component - forecast = [ + return [ { ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(), ATTR_FORECAST_TEMP: item["TemperatureMax"]["Value"], @@ -183,7 +184,6 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity): } for item in self.coordinator.data[ATTR_FORECAST] ] - return forecast @staticmethod def _calc_precipitation(day: dict[str, Any]) -> float: diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index d28cb51870b..ec99f2a12ae 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio from datetime import datetime, timedelta import logging -from typing import Any, Final, TypedDict +from typing import Final, TypedDict import aiohttp import async_timeout @@ -31,6 +31,7 @@ from homeassistant.components.weather import ( ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, + Forecast, WeatherEntity, ) from homeassistant.config_entries import ConfigEntry @@ -235,12 +236,12 @@ class SmhiWeather(WeatherEntity): return "Swedish weather institute (SMHI)" @property - def forecast(self) -> list[dict[str, Any]] | None: + def forecast(self) -> list[Forecast] | None: """Return the forecast.""" if self._forecasts is None or len(self._forecasts) < 2: return None - data = [] + data: list[Forecast] = [] for forecast in self._forecasts[1:]: condition = next( diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index a60011c205f..b737129e69e 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -1,7 +1,9 @@ """Weather component that handles meteorological data for your location.""" +from __future__ import annotations + from datetime import timedelta import logging -from typing import final +from typing import Final, TypedDict, final from homeassistant.config_entries import ConfigEntry from homeassistant.const import PRECISION_TENTHS, PRECISION_WHOLE, TEMP_CELSIUS @@ -35,15 +37,15 @@ ATTR_CONDITION_SUNNY = "sunny" ATTR_CONDITION_WINDY = "windy" ATTR_CONDITION_WINDY_VARIANT = "windy-variant" ATTR_FORECAST = "forecast" -ATTR_FORECAST_CONDITION = "condition" -ATTR_FORECAST_PRECIPITATION = "precipitation" -ATTR_FORECAST_PRECIPITATION_PROBABILITY = "precipitation_probability" -ATTR_FORECAST_PRESSURE = "pressure" -ATTR_FORECAST_TEMP = "temperature" -ATTR_FORECAST_TEMP_LOW = "templow" -ATTR_FORECAST_TIME = "datetime" -ATTR_FORECAST_WIND_BEARING = "wind_bearing" -ATTR_FORECAST_WIND_SPEED = "wind_speed" +ATTR_FORECAST_CONDITION: Final = "condition" +ATTR_FORECAST_PRECIPITATION: Final = "precipitation" +ATTR_FORECAST_PRECIPITATION_PROBABILITY: Final = "precipitation_probability" +ATTR_FORECAST_PRESSURE: Final = "pressure" +ATTR_FORECAST_TEMP: Final = "temperature" +ATTR_FORECAST_TEMP_LOW: Final = "templow" +ATTR_FORECAST_TIME: Final = "datetime" +ATTR_FORECAST_WIND_BEARING: Final = "wind_bearing" +ATTR_FORECAST_WIND_SPEED: Final = "wind_speed" ATTR_WEATHER_ATTRIBUTION = "attribution" ATTR_WEATHER_HUMIDITY = "humidity" ATTR_WEATHER_OZONE = "ozone" @@ -60,6 +62,20 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}" SCAN_INTERVAL = timedelta(seconds=30) +class Forecast(TypedDict, total=False): + """Typed weather forecast dict.""" + + condition: str | None + datetime: str + precipitation_probability: int | None + precipitation: float | None + pressure: float | None + temperature: float | None + templow: float | None + wind_bearing: float | str | None + wind_speed: float | None + + async def async_setup(hass, config): """Set up the weather component.""" component = hass.data[DOMAIN] = EntityComponent( @@ -84,59 +100,75 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: class WeatherEntity(Entity): """ABC for weather data.""" + _attr_attribution: str | None = None + _attr_condition: str | None + _attr_forecast: list[Forecast] | None = None + _attr_humidity: float | None = None + _attr_ozone: float | None = None + _attr_precision: float + _attr_pressure: float | None = None + _attr_state: None = None + _attr_temperature_unit: str + _attr_temperature: float | None + _attr_visibility: float | None = None + _attr_wind_bearing: float | str | None = None + _attr_wind_speed: float | None = None + @property - def temperature(self): + def temperature(self) -> float | None: """Return the platform temperature.""" - raise NotImplementedError() + return self._attr_temperature @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" - raise NotImplementedError() + return self._attr_temperature_unit @property - def pressure(self): + def pressure(self) -> float | None: """Return the pressure.""" - return None + return self._attr_pressure @property - def humidity(self): + def humidity(self) -> float | None: """Return the humidity.""" - raise NotImplementedError() + return self._attr_humidity @property - def wind_speed(self): + def wind_speed(self) -> float | None: """Return the wind speed.""" - return None + return self._attr_wind_speed @property - def wind_bearing(self): + def wind_bearing(self) -> float | str | None: """Return the wind bearing.""" - return None + return self._attr_wind_bearing @property - def ozone(self): + def ozone(self) -> float | None: """Return the ozone level.""" - return None + return self._attr_ozone @property - def attribution(self): + def attribution(self) -> str | None: """Return the attribution.""" - return None + return self._attr_attribution @property - def visibility(self): + def visibility(self) -> float | None: """Return the visibility.""" - return None + return self._attr_visibility @property - def forecast(self): + def forecast(self) -> list[Forecast] | None: """Return the forecast.""" - return None + return self._attr_forecast @property - def precision(self): + def precision(self) -> float: """Return the precision of the temperature value.""" + if hasattr(self, "_attr_precision"): + return self._attr_precision return ( PRECISION_TENTHS if self.temperature_unit == TEMP_CELSIUS @@ -205,11 +237,12 @@ class WeatherEntity(Entity): return data @property - def state(self): + @final + def state(self) -> str | None: """Return the current state.""" return self.condition @property - def condition(self): + def condition(self) -> str | None: """Return the current condition.""" - raise NotImplementedError() + return self._attr_condition