Remove deprecated forecast attribute from WeatherEntity (#110761)

* Remove deprecated forecast attribute from WeatherEntity

* Fix some

* Ruff

* ipma

* buienradar

* some more

* Some more

* More and more

* strings

* attr_forecast

* Fix nws

* Fix

* remove from coverage

* Remove recorder test

* Review comments
This commit is contained in:
G Johansson 2024-03-27 16:51:29 +01:00 committed by GitHub
parent 1269031d11
commit 65230908c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 37 additions and 1596 deletions

View file

@ -956,7 +956,9 @@ omit =
homeassistant/components/openuv/binary_sensor.py homeassistant/components/openuv/binary_sensor.py
homeassistant/components/openuv/coordinator.py homeassistant/components/openuv/coordinator.py
homeassistant/components/openuv/sensor.py homeassistant/components/openuv/sensor.py
homeassistant/components/openweathermap/__init__.py
homeassistant/components/openweathermap/sensor.py homeassistant/components/openweathermap/sensor.py
homeassistant/components/openweathermap/weather.py
homeassistant/components/openweathermap/weather_update_coordinator.py homeassistant/components/openweathermap/weather_update_coordinator.py
homeassistant/components/opnsense/__init__.py homeassistant/components/opnsense/__init__.py
homeassistant/components/opower/__init__.py homeassistant/components/opower/__init__.py

View file

@ -146,9 +146,9 @@ class AccuWeatherEntity(
"""Return the UV index.""" """Return the UV index."""
return cast(float, self.coordinator.data["UVIndex"]) return cast(float, self.coordinator.data["UVIndex"])
@property @callback
def forecast(self) -> list[Forecast] | None: def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the forecast array.""" """Return the daily forecast in native units."""
if not self.coordinator.forecast: if not self.coordinator.forecast:
return None return None
# remap keys from library to keys understood by the weather component # remap keys from library to keys understood by the weather component
@ -177,8 +177,3 @@ class AccuWeatherEntity(
} }
for item in self.coordinator.data[ATTR_FORECAST] for item in self.coordinator.data[ATTR_FORECAST]
] ]
@callback
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""
return self.forecast

View file

@ -138,13 +138,14 @@ class BrWeather(WeatherEntity):
self._attr_unique_id = ( self._attr_unique_id = (
f"{coordinates[CONF_LATITUDE]:2.6f}{coordinates[CONF_LONGITUDE]:2.6f}" f"{coordinates[CONF_LATITUDE]:2.6f}{coordinates[CONF_LONGITUDE]:2.6f}"
) )
self._forecast: list | None = None
@callback @callback
def data_updated(self, data: BrData) -> None: def data_updated(self, data: BrData) -> None:
"""Update data.""" """Update data."""
self._attr_attribution = data.attribution self._attr_attribution = data.attribution
self._attr_condition = self._calc_condition(data) self._attr_condition = self._calc_condition(data)
self._attr_forecast = self._calc_forecast(data) self._forecast = self._calc_forecast(data)
self._attr_humidity = data.humidity self._attr_humidity = data.humidity
self._attr_name = ( self._attr_name = (
self._stationname or f"BR {data.stationname or '(unknown station)'}" self._stationname or f"BR {data.stationname or '(unknown station)'}"
@ -196,4 +197,4 @@ class BrWeather(WeatherEntity):
async def async_forecast_daily(self) -> list[Forecast] | None: async def async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""
return self._attr_forecast return self._forecast

View file

@ -184,11 +184,6 @@ class EcobeeWeather(WeatherEntity):
return forecasts return forecasts
return None return None
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
return self._forecast()
async def async_forecast_daily(self) -> list[Forecast] | None: async def async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""
return self._forecast() return self._forecast()

View file

@ -174,11 +174,6 @@ class ECWeather(SingleCoordinatorWeatherEntity):
return icon_code_to_condition(int(icon_code)) return icon_code_to_condition(int(icon_code))
return "" return ""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
return get_forecast(self.ec_data, False)
@callback @callback
def _async_forecast_daily(self) -> list[Forecast] | None: def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""

View file

@ -205,13 +205,6 @@ class IPMAWeather(WeatherEntity, IPMADevice):
for data_in in forecast for data_in in forecast
] ]
@property
def forecast(self) -> list[Forecast]:
"""Return the forecast array."""
return self._forecast(
self._hourly_forecast if self._period == 1 else self._daily_forecast
)
async def _try_update_forecast( async def _try_update_forecast(
self, self,
forecast_type: Literal["daily", "hourly"], forecast_type: Literal["daily", "hourly"],

View file

@ -61,29 +61,6 @@ async def async_setup_entry(
"""Set up the Demo config entry.""" """Set up the Demo config entry."""
async_add_entities( async_add_entities(
[ [
DemoWeather(
"Legacy weather",
"Sunshine",
21.6414,
92,
1099,
0.5,
UnitOfTemperature.CELSIUS,
UnitOfPressure.HPA,
UnitOfSpeed.METERS_PER_SECOND,
[
[ATTR_CONDITION_RAINY, 1, 22, 15, 60],
[ATTR_CONDITION_RAINY, 5, 19, 8, 30],
[ATTR_CONDITION_CLOUDY, 0, 15, 9, 10],
[ATTR_CONDITION_SUNNY, 0, 12, 6, 0],
[ATTR_CONDITION_PARTLYCLOUDY, 2, 14, 7, 20],
[ATTR_CONDITION_RAINY, 15, 18, 7, 0],
[ATTR_CONDITION_FOG, 0.2, 21, 12, 100],
],
None,
None,
None,
),
DemoWeather( DemoWeather(
"Legacy + daily weather", "Legacy + daily weather",
"Sunshine", "Sunshine",
@ -103,15 +80,6 @@ async def async_setup_entry(
[ATTR_CONDITION_RAINY, 15, 18, 7, 0], [ATTR_CONDITION_RAINY, 15, 18, 7, 0],
[ATTR_CONDITION_FOG, 0.2, 21, 12, 100], [ATTR_CONDITION_FOG, 0.2, 21, 12, 100],
], ],
[
[ATTR_CONDITION_RAINY, 1, 22, 15, 60],
[ATTR_CONDITION_RAINY, 5, 19, 8, 30],
[ATTR_CONDITION_CLOUDY, 0, 15, 9, 10],
[ATTR_CONDITION_SUNNY, 0, 12, 6, 0],
[ATTR_CONDITION_PARTLYCLOUDY, 2, 14, 7, 20],
[ATTR_CONDITION_RAINY, 15, 18, 7, 0],
[ATTR_CONDITION_FOG, 0.2, 21, 12, 100],
],
None, None,
None, None,
), ),
@ -125,7 +93,6 @@ async def async_setup_entry(
UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.FAHRENHEIT,
UnitOfPressure.INHG, UnitOfPressure.INHG,
UnitOfSpeed.MILES_PER_HOUR, UnitOfSpeed.MILES_PER_HOUR,
None,
[ [
[ATTR_CONDITION_SNOWY, 2, -10, -15, 60], [ATTR_CONDITION_SNOWY, 2, -10, -15, 60],
[ATTR_CONDITION_PARTLYCLOUDY, 1, -13, -14, 25], [ATTR_CONDITION_PARTLYCLOUDY, 1, -13, -14, 25],
@ -156,7 +123,6 @@ async def async_setup_entry(
UnitOfTemperature.CELSIUS, UnitOfTemperature.CELSIUS,
UnitOfPressure.HPA, UnitOfPressure.HPA,
UnitOfSpeed.METERS_PER_SECOND, UnitOfSpeed.METERS_PER_SECOND,
None,
[ [
[ATTR_CONDITION_RAINY, 1, 22, 15, 60], [ATTR_CONDITION_RAINY, 1, 22, 15, 60],
[ATTR_CONDITION_RAINY, 5, 19, 8, 30], [ATTR_CONDITION_RAINY, 5, 19, 8, 30],
@ -196,7 +162,6 @@ async def async_setup_entry(
UnitOfPressure.HPA, UnitOfPressure.HPA,
UnitOfSpeed.METERS_PER_SECOND, UnitOfSpeed.METERS_PER_SECOND,
None, None,
None,
[ [
[ATTR_CONDITION_CLOUDY, 1, 22, 15, 60], [ATTR_CONDITION_CLOUDY, 1, 22, 15, 60],
[ATTR_CONDITION_CLOUDY, 5, 19, 8, 30], [ATTR_CONDITION_CLOUDY, 5, 19, 8, 30],
@ -226,7 +191,6 @@ async def async_setup_entry(
UnitOfTemperature.CELSIUS, UnitOfTemperature.CELSIUS,
UnitOfPressure.HPA, UnitOfPressure.HPA,
UnitOfSpeed.METERS_PER_SECOND, UnitOfSpeed.METERS_PER_SECOND,
None,
[ [
[ATTR_CONDITION_RAINY, 1, 22, 15, 60], [ATTR_CONDITION_RAINY, 1, 22, 15, 60],
[ATTR_CONDITION_RAINY, 5, 19, 8, 30], [ATTR_CONDITION_RAINY, 5, 19, 8, 30],
@ -268,7 +232,6 @@ class DemoWeather(WeatherEntity):
temperature_unit: str, temperature_unit: str,
pressure_unit: str, pressure_unit: str,
wind_speed_unit: str, wind_speed_unit: str,
forecast: list[list] | None,
forecast_daily: list[list] | None, forecast_daily: list[list] | None,
forecast_hourly: list[list] | None, forecast_hourly: list[list] | None,
forecast_twice_daily: list[list] | None, forecast_twice_daily: list[list] | None,
@ -284,7 +247,6 @@ class DemoWeather(WeatherEntity):
self._native_pressure_unit = pressure_unit self._native_pressure_unit = pressure_unit
self._native_wind_speed = wind_speed self._native_wind_speed = wind_speed
self._native_wind_speed_unit = wind_speed_unit self._native_wind_speed_unit = wind_speed_unit
self._forecast = forecast
self._forecast_daily = forecast_daily self._forecast_daily = forecast_daily
self._forecast_hourly = forecast_hourly self._forecast_hourly = forecast_hourly
self._forecast_twice_daily = forecast_twice_daily self._forecast_twice_daily = forecast_twice_daily
@ -360,28 +322,6 @@ class DemoWeather(WeatherEntity):
"""Return the weather condition.""" """Return the weather condition."""
return CONDITION_MAP[self._condition.lower()] return CONDITION_MAP[self._condition.lower()]
@property
def forecast(self) -> list[Forecast]:
"""Return legacy forecast."""
if self._forecast is None:
return []
reftime = dt_util.now().replace(hour=16, minute=00)
forecast_data = []
for entry in self._forecast:
data_dict = Forecast(
datetime=reftime.isoformat(),
condition=entry[0],
precipitation=entry[1],
temperature=entry[2],
templow=entry[3],
precipitation_probability=entry[4],
)
reftime = reftime + timedelta(hours=24)
forecast_data.append(data_dict)
return forecast_data
async def async_forecast_daily(self) -> list[Forecast]: async def async_forecast_daily(self) -> list[Forecast]:
"""Return the daily forecast.""" """Return the daily forecast."""
if self._forecast_daily is None: if self._forecast_daily is None:

View file

@ -231,11 +231,6 @@ class MetWeather(SingleCoordinatorWeatherEntity[MetDataUpdateCoordinator]):
ha_forecast.append(ha_item) # type: ignore[arg-type] ha_forecast.append(ha_item) # type: ignore[arg-type]
return ha_forecast return ha_forecast
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
return self._forecast(False)
@callback @callback
def _async_forecast_daily(self) -> list[Forecast] | None: def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""

View file

@ -173,11 +173,6 @@ class MetEireannWeather(
ha_forecast.append(ha_item) ha_forecast.append(ha_item)
return ha_forecast return ha_forecast
@property
def forecast(self) -> list[Forecast]:
"""Return the forecast array."""
return self._forecast(False)
@callback @callback
def _async_forecast_daily(self) -> list[Forecast]: def _async_forecast_daily(self) -> list[Forecast]:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""

View file

@ -216,11 +216,6 @@ class MeteoFranceWeather(
) )
return forecast_data 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]: async def async_forecast_daily(self) -> list[Forecast]:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""
return self._forecast(FORECAST_MODE_DAILY) return self._forecast(FORECAST_MODE_DAILY)

View file

@ -181,14 +181,6 @@ class MetOfficeWeather(
return str(value) if value is not None else None return str(value) if value is not None else None
return None return None
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
return [
_build_forecast_data(timestep)
for timestep in self.coordinator.data.forecast
]
@callback @callback
def _async_forecast_daily(self) -> list[Forecast] | None: def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the twice daily forecast in native units.""" """Return the twice daily forecast in native units."""

View file

@ -299,11 +299,6 @@ class NWSWeather(CoordinatorWeatherEntity):
forecast.append(data) forecast.append(data)
return forecast return forecast
@property
def forecast(self) -> list[Forecast] | None:
"""Return forecast."""
return self._forecast(self._forecast_legacy, DAYNIGHT)
@callback @callback
def _async_forecast_hourly(self) -> list[Forecast] | None: def _async_forecast_hourly(self) -> list[Forecast] | None:
"""Return the hourly forecast in native units.""" """Return the hourly forecast in native units."""

View file

@ -88,9 +88,9 @@ class OpenMeteoWeatherEntity(
return None return None
return self.coordinator.data.current_weather.wind_direction return self.coordinator.data.current_weather.wind_direction
@property @callback
def forecast(self) -> list[Forecast] | None: def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the forecast in native units.""" """Return the daily forecast in native units."""
if self.coordinator.data.daily is None: if self.coordinator.data.daily is None:
return None return None
@ -124,8 +124,3 @@ class OpenMeteoWeatherEntity(
forecasts.append(forecast) forecasts.append(forecast)
return forecasts return forecasts
@callback
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""
return self.forecast

View file

@ -185,7 +185,7 @@ class OpenWeatherMapWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordina
return self.coordinator.data[ATTR_API_WIND_BEARING] return self.coordinator.data[ATTR_API_WIND_BEARING]
@property @property
def forecast(self) -> list[Forecast] | None: def _forecast(self) -> list[Forecast] | None:
"""Return the forecast array.""" """Return the forecast array."""
api_forecasts = self.coordinator.data[ATTR_API_FORECAST] api_forecasts = self.coordinator.data[ATTR_API_FORECAST]
forecasts = [ forecasts = [
@ -201,9 +201,9 @@ class OpenWeatherMapWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordina
@callback @callback
def _async_forecast_daily(self) -> list[Forecast] | None: def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""
return self.forecast return self._forecast
@callback @callback
def _async_forecast_hourly(self) -> list[Forecast] | None: def _async_forecast_hourly(self) -> list[Forecast] | None:
"""Return the hourly forecast in native units.""" """Return the hourly forecast in native units."""
return self.forecast return self._forecast

View file

@ -195,35 +195,6 @@ class SmhiWeather(WeatherEntity):
"""Retry refresh weather forecast.""" """Retry refresh weather forecast."""
await self.async_update(no_throttle=True) await self.async_update(no_throttle=True)
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
if self._forecast_daily is None or len(self._forecast_daily) < 2:
return None
data: list[Forecast] = []
for forecast in self._forecast_daily[1:]:
condition = CONDITION_MAP.get(forecast.symbol)
data.append(
{
ATTR_FORECAST_TIME: forecast.valid_time.isoformat(),
ATTR_FORECAST_NATIVE_TEMP: forecast.temperature_max,
ATTR_FORECAST_NATIVE_TEMP_LOW: forecast.temperature_min,
ATTR_FORECAST_NATIVE_PRECIPITATION: forecast.total_precipitation,
ATTR_FORECAST_CONDITION: condition,
ATTR_FORECAST_NATIVE_PRESSURE: forecast.pressure,
ATTR_FORECAST_WIND_BEARING: forecast.wind_direction,
ATTR_FORECAST_NATIVE_WIND_SPEED: forecast.wind_speed,
ATTR_FORECAST_HUMIDITY: forecast.humidity,
ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: forecast.wind_gust,
ATTR_FORECAST_CLOUD_COVERAGE: forecast.cloudiness,
}
)
return data
def _get_forecast_data( def _get_forecast_data(
self, forecast_data: list[SmhiForecast] | None self, forecast_data: list[SmhiForecast] | None
) -> list[Forecast] | None: ) -> list[Forecast] | None:

View file

@ -118,7 +118,6 @@ WEATHER_SCHEMA = vol.Schema(
vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template, vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template,
vol.Optional(CONF_OZONE_TEMPLATE): cv.template, vol.Optional(CONF_OZONE_TEMPLATE): cv.template,
vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template, vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template,
vol.Optional(CONF_FORECAST_TEMPLATE): cv.template,
vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template, vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template,
vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template, vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template,
vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template, vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template,
@ -193,7 +192,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
self._wind_bearing_template = config.get(CONF_WIND_BEARING_TEMPLATE) self._wind_bearing_template = config.get(CONF_WIND_BEARING_TEMPLATE)
self._ozone_template = config.get(CONF_OZONE_TEMPLATE) self._ozone_template = config.get(CONF_OZONE_TEMPLATE)
self._visibility_template = config.get(CONF_VISIBILITY_TEMPLATE) self._visibility_template = config.get(CONF_VISIBILITY_TEMPLATE)
self._forecast_template = config.get(CONF_FORECAST_TEMPLATE)
self._forecast_daily_template = config.get(CONF_FORECAST_DAILY_TEMPLATE) self._forecast_daily_template = config.get(CONF_FORECAST_DAILY_TEMPLATE)
self._forecast_hourly_template = config.get(CONF_FORECAST_HOURLY_TEMPLATE) self._forecast_hourly_template = config.get(CONF_FORECAST_HOURLY_TEMPLATE)
self._forecast_twice_daily_template = config.get( self._forecast_twice_daily_template = config.get(
@ -227,7 +225,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
self._cloud_coverage = None self._cloud_coverage = None
self._dew_point = None self._dew_point = None
self._apparent_temperature = None self._apparent_temperature = None
self._forecast: list[Forecast] = []
self._forecast_daily: list[Forecast] = [] self._forecast_daily: list[Forecast] = []
self._forecast_hourly: list[Forecast] = [] self._forecast_hourly: list[Forecast] = []
self._forecast_twice_daily: list[Forecast] = [] self._forecast_twice_daily: list[Forecast] = []
@ -300,11 +297,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
"""Return the apparent temperature.""" """Return the apparent temperature."""
return self._apparent_temperature return self._apparent_temperature
@property
def forecast(self) -> list[Forecast]:
"""Return the forecast."""
return self._forecast
async def async_forecast_daily(self) -> list[Forecast]: async def async_forecast_daily(self) -> list[Forecast]:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""
return self._forecast_daily return self._forecast_daily
@ -394,11 +386,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
"_apparent_temperature", "_apparent_temperature",
self._apparent_temperature_template, self._apparent_temperature_template,
) )
if self._forecast_template:
self.add_template_attribute(
"_forecast",
self._forecast_template,
)
if self._forecast_daily_template: if self._forecast_daily_template:
self.add_template_attribute( self.add_template_attribute(

View file

@ -298,11 +298,6 @@ class TomorrowioWeatherEntity(TomorrowioEntity, SingleCoordinatorWeatherEntity):
return forecasts return forecasts
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
return self._forecast(self.forecast_type)
@callback @callback
def _async_forecast_daily(self) -> list[Forecast] | None: def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""

View file

@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
import abc import abc
import asyncio
from collections.abc import Callable, Iterable from collections.abc import Callable, Iterable
from contextlib import suppress from contextlib import suppress
from datetime import timedelta from datetime import timedelta
@ -48,7 +47,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
) )
from homeassistant.helpers.entity import ABCCachedProperties, Entity, EntityDescription from homeassistant.helpers.entity import ABCCachedProperties, Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
import homeassistant.helpers.issue_registry as ir import homeassistant.helpers.issue_registry as ir
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
@ -56,7 +54,6 @@ from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator, DataUpdateCoordinator,
TimestampDataUpdateCoordinator, TimestampDataUpdateCoordinator,
) )
from homeassistant.loader import async_get_issue_tracker, async_suggest_report_issue
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
from homeassistant.util.json import JsonValueType from homeassistant.util.json import JsonValueType
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
@ -111,7 +108,6 @@ ATTR_CONDITION_SNOWY_RAINY = "snowy-rainy"
ATTR_CONDITION_SUNNY = "sunny" ATTR_CONDITION_SUNNY = "sunny"
ATTR_CONDITION_WINDY = "windy" ATTR_CONDITION_WINDY = "windy"
ATTR_CONDITION_WINDY_VARIANT = "windy-variant" ATTR_CONDITION_WINDY_VARIANT = "windy-variant"
ATTR_FORECAST = "forecast"
ATTR_FORECAST_IS_DAYTIME: Final = "is_daytime" ATTR_FORECAST_IS_DAYTIME: Final = "is_daytime"
ATTR_FORECAST_CONDITION: Final = "condition" ATTR_FORECAST_CONDITION: Final = "condition"
ATTR_FORECAST_HUMIDITY: Final = "humidity" ATTR_FORECAST_HUMIDITY: Final = "humidity"
@ -306,13 +302,8 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""ABC for weather data.""" """ABC for weather data."""
_entity_component_unrecorded_attributes = frozenset({ATTR_FORECAST})
entity_description: WeatherEntityDescription entity_description: WeatherEntityDescription
_attr_condition: str | None = None _attr_condition: str | None = None
# _attr_forecast is deprecated, implement async_forecast_daily,
# async_forecast_hourly or async_forecast_twice daily instead
_attr_forecast: list[Forecast] | None = None
_attr_humidity: float | None = None _attr_humidity: float | None = None
_attr_ozone: float | None = None _attr_ozone: float | None = None
_attr_cloud_coverage: int | None = None _attr_cloud_coverage: int | None = None
@ -338,8 +329,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A
Literal["daily", "hourly", "twice_daily"], Literal["daily", "hourly", "twice_daily"],
list[Callable[[list[JsonValueType] | None], None]], list[Callable[[list[JsonValueType] | None], None]],
] ]
__weather_reported_legacy_forecast = False
__weather_legacy_forecast = False
_weather_option_temperature_unit: str | None = None _weather_option_temperature_unit: str | None = None
_weather_option_pressure_unit: str | None = None _weather_option_pressure_unit: str | None = None
@ -351,77 +340,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A
"""Finish initializing.""" """Finish initializing."""
self._forecast_listeners = {"daily": [], "hourly": [], "twice_daily": []} self._forecast_listeners = {"daily": [], "hourly": [], "twice_daily": []}
def __init_subclass__(cls, **kwargs: Any) -> None:
"""Post initialisation processing."""
super().__init_subclass__(**kwargs)
if (
"forecast" in cls.__dict__
and cls.async_forecast_daily is WeatherEntity.async_forecast_daily
and cls.async_forecast_hourly is WeatherEntity.async_forecast_hourly
and cls.async_forecast_twice_daily
is WeatherEntity.async_forecast_twice_daily
):
cls.__weather_legacy_forecast = True
@callback
def add_to_platform_start(
self,
hass: HomeAssistant,
platform: EntityPlatform,
parallel_updates: asyncio.Semaphore | None,
) -> None:
"""Start adding an entity to a platform."""
super().add_to_platform_start(hass, platform, parallel_updates)
if self.__weather_legacy_forecast:
self._report_legacy_forecast(hass)
def _report_legacy_forecast(self, hass: HomeAssistant) -> None:
"""Log warning and create an issue if the entity imlpements legacy forecast."""
if "custom_components" not in type(self).__module__:
# Do not report core integrations as they are already fixed or PR is open.
return
report_issue = async_suggest_report_issue(
hass,
integration_domain=self.platform.platform_name,
module=type(self).__module__,
)
_LOGGER.warning(
(
"%s::%s implements the `forecast` property or sets "
"`self._attr_forecast` in a subclass of WeatherEntity, this is "
"deprecated and will be unsupported from Home Assistant 2024.3."
" Please %s"
),
self.platform.platform_name,
self.__class__.__name__,
report_issue,
)
translation_placeholders = {"platform": self.platform.platform_name}
translation_key = "deprecated_weather_forecast_no_url"
issue_tracker = async_get_issue_tracker(
hass,
integration_domain=self.platform.platform_name,
module=type(self).__module__,
)
if issue_tracker:
translation_placeholders["issue_tracker"] = issue_tracker
translation_key = "deprecated_weather_forecast_url"
ir.async_create_issue(
self.hass,
DOMAIN,
f"deprecated_weather_forecast_{self.platform.platform_name}",
breaks_in_ha_version="2024.3.0",
is_fixable=False,
is_persistent=False,
issue_domain=self.platform.platform_name,
severity=ir.IssueSeverity.WARNING,
translation_key=translation_key,
translation_placeholders=translation_placeholders,
)
self.__weather_reported_legacy_forecast = True
async def async_internal_added_to_hass(self) -> None: async def async_internal_added_to_hass(self) -> None:
"""Call when the weather entity is added to hass.""" """Call when the weather entity is added to hass."""
await super().async_internal_added_to_hass() await super().async_internal_added_to_hass()
@ -605,23 +523,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A
return self._default_visibility_unit return self._default_visibility_unit
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast in native units.
Should not be overridden by integrations. Kept for backwards compatibility.
"""
if (
self._attr_forecast is not None
and type(self).async_forecast_daily is WeatherEntity.async_forecast_daily
and type(self).async_forecast_hourly is WeatherEntity.async_forecast_hourly
and type(self).async_forecast_twice_daily
is WeatherEntity.async_forecast_twice_daily
and not self.__weather_reported_legacy_forecast
):
self._report_legacy_forecast(self.hass)
return self._attr_forecast
async def async_forecast_daily(self) -> list[Forecast] | None: async def async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units.""" """Return the daily forecast in native units."""
raise NotImplementedError raise NotImplementedError
@ -804,9 +705,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A
data[ATTR_WEATHER_VISIBILITY_UNIT] = self._visibility_unit data[ATTR_WEATHER_VISIBILITY_UNIT] = self._visibility_unit
data[ATTR_WEATHER_PRECIPITATION_UNIT] = self._precipitation_unit data[ATTR_WEATHER_PRECIPITATION_UNIT] = self._precipitation_unit
if self.forecast:
data[ATTR_FORECAST] = self._convert_forecast(self.forecast)
return data return data
@final @final

View file

@ -110,14 +110,6 @@
} }
}, },
"issues": { "issues": {
"deprecated_weather_forecast_url": {
"title": "The {platform} custom integration is using deprecated weather forecast",
"description": "The custom integration `{platform}` implements the `forecast` property or sets `self._attr_forecast` in a subclass of WeatherEntity.\n\nPlease create a bug report at {issue_tracker}.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue."
},
"deprecated_weather_forecast_no_url": {
"title": "[%key:component::weather::issues::deprecated_weather_forecast_url::title%]",
"description": "The custom integration `{platform}` implements the `forecast` property or sets `self._attr_forecast` in a subclass of WeatherEntity.\n\nPlease report it to the author of the {platform} integration.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue."
},
"deprecated_service_weather_get_forecast": { "deprecated_service_weather_get_forecast": {
"title": "Detected use of deprecated service `weather.get_forecast`", "title": "Detected use of deprecated service `weather.get_forecast`",
"fix_flow": { "fix_flow": {

View file

@ -9,18 +9,7 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.accuweather.const import ATTRIBUTION from homeassistant.components.accuweather.const import ATTRIBUTION
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_FORECAST_APPARENT_TEMP,
ATTR_FORECAST_CLOUD_COVERAGE,
ATTR_FORECAST_CONDITION, ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_GUST_SPEED,
ATTR_FORECAST_WIND_SPEED,
ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_APPARENT_TEMPERATURE,
ATTR_WEATHER_CLOUD_COVERAGE, ATTR_WEATHER_CLOUD_COVERAGE,
ATTR_WEATHER_DEW_POINT, ATTR_WEATHER_DEW_POINT,
@ -35,7 +24,6 @@ from homeassistant.components.weather import (
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
LEGACY_SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS, SERVICE_GET_FORECASTS,
WeatherEntityFeature,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
@ -58,16 +46,13 @@ from tests.common import (
from tests.typing import WebSocketGenerator from tests.typing import WebSocketGenerator
async def test_weather_without_forecast( async def test_weather(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> None:
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test states of the weather without forecast.""" """Test states of the weather without forecast."""
await init_integration(hass) await init_integration(hass)
state = hass.states.get("weather.home") state = hass.states.get("weather.home")
assert state assert state
assert state.state == "sunny" assert state.state == "sunny"
assert not state.attributes.get(ATTR_FORECAST)
assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 67 assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 67
assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1012.0 assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1012.0
assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6 assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6
@ -87,49 +72,6 @@ async def test_weather_without_forecast(
assert entry.unique_id == "0123456" assert entry.unique_id == "0123456"
async def test_weather_with_forecast(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test states of the weather with forecast."""
await init_integration(hass, forecast=True)
state = hass.states.get("weather.home")
assert state
assert state.state == "sunny"
assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 67
assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1012.0
assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6
assert state.attributes.get(ATTR_WEATHER_VISIBILITY) == 16.1
assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 180
assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 14.5 # 4.03 m/s -> km/h
assert state.attributes.get(ATTR_WEATHER_APPARENT_TEMPERATURE) == 22.8
assert state.attributes.get(ATTR_WEATHER_DEW_POINT) == 16.2
assert state.attributes.get(ATTR_WEATHER_CLOUD_COVERAGE) == 10
assert state.attributes.get(ATTR_WEATHER_WIND_GUST_SPEED) == 20.3
assert state.attributes.get(ATTR_WEATHER_UV_INDEX) == 6
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes[ATTR_SUPPORTED_FEATURES] == WeatherEntityFeature.FORECAST_DAILY
)
forecast = state.attributes.get(ATTR_FORECAST)[0]
assert forecast.get(ATTR_FORECAST_CONDITION) == "lightning-rainy"
assert forecast.get(ATTR_FORECAST_PRECIPITATION) == 2.5
assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 60
assert forecast.get(ATTR_FORECAST_TEMP) == 29.5
assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 15.4
assert forecast.get(ATTR_FORECAST_TIME) == "2020-07-26T05:00:00+00:00"
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 166
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 13.0 # 3.61 m/s -> km/h
assert forecast.get(ATTR_FORECAST_CLOUD_COVERAGE) == 58
assert forecast.get(ATTR_FORECAST_APPARENT_TEMP) == 29.8
assert forecast.get(ATTR_FORECAST_WIND_GUST_SPEED) == 29.6
assert forecast.get(ATTR_WEATHER_UV_INDEX) == 5
entry = entity_registry.async_get("weather.home")
assert entry
assert entry.unique_id == "0123456"
async def test_availability(hass: HomeAssistant) -> None: async def test_availability(hass: HomeAssistant) -> None:
"""Ensure that we mark the entities unavailable correctly when service is offline.""" """Ensure that we mark the entities unavailable correctly when service is offline."""
await init_integration(hass) await init_integration(hass)

View file

@ -9,14 +9,6 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.ipma.const import MIN_TIME_BETWEEN_UPDATES from homeassistant.components.ipma.const import MIN_TIME_BETWEEN_UPDATES
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED,
ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_HUMIDITY,
ATTR_WEATHER_PRESSURE, ATTR_WEATHER_PRESSURE,
ATTR_WEATHER_TEMPERATURE, ATTR_WEATHER_TEMPERATURE,
@ -84,53 +76,6 @@ async def test_setup_config_flow(hass: HomeAssistant) -> None:
assert state.attributes.get("friendly_name") == "HomeTown" assert state.attributes.get("friendly_name") == "HomeTown"
async def test_daily_forecast(hass: HomeAssistant) -> None:
"""Test for successfully getting daily forecast."""
with patch(
"pyipma.location.Location.get",
return_value=MockLocation(),
):
entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.hometown")
assert state.state == "rainy"
forecast = state.attributes.get(ATTR_FORECAST)[0]
assert forecast.get(ATTR_FORECAST_TIME) == datetime.datetime(2020, 1, 16, 0, 0, 0)
assert forecast.get(ATTR_FORECAST_CONDITION) == "rainy"
assert forecast.get(ATTR_FORECAST_TEMP) == 16.2
assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 10.6
assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == "100.0"
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 10.0
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == "S"
@pytest.mark.freeze_time("2020-01-14 23:00:00")
async def test_hourly_forecast(hass: HomeAssistant) -> None:
"""Test for successfully getting daily forecast."""
with patch(
"pyipma.location.Location.get",
return_value=MockLocation(),
):
entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG_HOURLY)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("weather.hometown")
assert state.state == "rainy"
forecast = state.attributes.get(ATTR_FORECAST)[0]
assert forecast.get(ATTR_FORECAST_CONDITION) == "rainy"
assert forecast.get(ATTR_FORECAST_TEMP) == 12.0
assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 80.0
assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 32.7
assert forecast.get(ATTR_FORECAST_WIND_BEARING) == "S"
async def test_failed_get_observation_forecast(hass: HomeAssistant) -> None: async def test_failed_get_observation_forecast(hass: HomeAssistant) -> None:
"""Test for successfully setting up the IPMA platform.""" """Test for successfully setting up the IPMA platform."""
with patch( with patch(

View file

@ -163,19 +163,6 @@ async def test_one_weather_site_running(
assert weather.attributes.get("wind_bearing") == "SSE" assert weather.attributes.get("wind_bearing") == "SSE"
assert weather.attributes.get("humidity") == 50 assert weather.attributes.get("humidity") == 50
# Also has Forecasts added - again, just pick out 1 entry to check
# ensures that daily filters out multiple results per day
assert len(weather.attributes.get("forecast")) == 4
assert (
weather.attributes.get("forecast")[3]["datetime"] == "2020-04-29T12:00:00+00:00"
)
assert weather.attributes.get("forecast")[3]["condition"] == "rainy"
assert weather.attributes.get("forecast")[3]["precipitation_probability"] == 59
assert weather.attributes.get("forecast")[3]["temperature"] == 13
assert weather.attributes.get("forecast")[3]["wind_speed"] == 20.92
assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE"
@pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) @pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC))
async def test_two_weather_sites_running( async def test_two_weather_sites_running(
@ -230,19 +217,6 @@ async def test_two_weather_sites_running(
assert weather.attributes.get("wind_bearing") == "SSE" assert weather.attributes.get("wind_bearing") == "SSE"
assert weather.attributes.get("humidity") == 50 assert weather.attributes.get("humidity") == 50
# Also has Forecasts added - again, just pick out 1 entry to check
# ensures that daily filters out multiple results per day
assert len(weather.attributes.get("forecast")) == 4
assert (
weather.attributes.get("forecast")[3]["datetime"] == "2020-04-29T12:00:00+00:00"
)
assert weather.attributes.get("forecast")[3]["condition"] == "rainy"
assert weather.attributes.get("forecast")[3]["precipitation_probability"] == 59
assert weather.attributes.get("forecast")[3]["temperature"] == 13
assert weather.attributes.get("forecast")[3]["wind_speed"] == 20.92
assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE"
# King's Lynn daily weather platform expected results # King's Lynn daily weather platform expected results
weather = hass.states.get("weather.met_office_king_s_lynn_daily") weather = hass.states.get("weather.met_office_king_s_lynn_daily")
assert weather assert weather
@ -254,19 +228,6 @@ async def test_two_weather_sites_running(
assert weather.attributes.get("wind_bearing") == "ESE" assert weather.attributes.get("wind_bearing") == "ESE"
assert weather.attributes.get("humidity") == 75 assert weather.attributes.get("humidity") == 75
# All should have Forecast added - again, just picking out 1 entry to check
# ensures daily filters out multiple results per day
assert len(weather.attributes.get("forecast")) == 4
assert (
weather.attributes.get("forecast")[2]["datetime"] == "2020-04-28T12:00:00+00:00"
)
assert weather.attributes.get("forecast")[2]["condition"] == "cloudy"
assert weather.attributes.get("forecast")[2]["precipitation_probability"] == 14
assert weather.attributes.get("forecast")[2]["temperature"] == 11
assert weather.attributes.get("forecast")[2]["wind_speed"] == 11.27
assert weather.attributes.get("forecast")[2]["wind_bearing"] == "ESE"
@pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) @pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC))
async def test_new_config_entry( async def test_new_config_entry(

View file

@ -12,7 +12,6 @@ from homeassistant.components import nws
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_SUNNY, ATTR_CONDITION_SUNNY,
ATTR_FORECAST,
DOMAIN as WEATHER_DOMAIN, DOMAIN as WEATHER_DOMAIN,
LEGACY_SERVICE_GET_FORECAST, LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS, SERVICE_GET_FORECASTS,
@ -77,10 +76,6 @@ async def test_imperial_metric(
for key, value in result_observation.items(): for key, value in result_observation.items():
assert data.get(key) == value assert data.get(key) == value
forecast = data.get(ATTR_FORECAST)
for key, value in result_forecast.items():
assert forecast[0].get(key) == value
async def test_night_clear(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: async def test_night_clear(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None:
"""Test with clear-night in observation.""" """Test with clear-night in observation."""
@ -119,10 +114,6 @@ async def test_none_values(hass: HomeAssistant, mock_simple_nws, no_sensor) -> N
for key in WEATHER_EXPECTED_OBSERVATION_IMPERIAL: for key in WEATHER_EXPECTED_OBSERVATION_IMPERIAL:
assert data.get(key) is None assert data.get(key) is None
forecast = data.get(ATTR_FORECAST)
for key in EXPECTED_FORECAST_IMPERIAL:
assert forecast[0].get(key) is None
async def test_none(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: async def test_none(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None:
"""Test with None as observation and forecast.""" """Test with None as observation and forecast."""
@ -146,9 +137,6 @@ async def test_none(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None:
for key in WEATHER_EXPECTED_OBSERVATION_IMPERIAL: for key in WEATHER_EXPECTED_OBSERVATION_IMPERIAL:
assert data.get(key) is None assert data.get(key) is None
forecast = data.get(ATTR_FORECAST)
assert forecast is None
async def test_error_station(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: async def test_error_station(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None:
"""Test error in setting station.""" """Test error in setting station."""

View file

@ -1,338 +1,4 @@
# serializer version: 1 # serializer version: 1
# name: test_forecast_daily
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T12:00:00',
'humidity': 96,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 18.0,
'templow': 15.0,
'wind_bearing': 114,
'wind_gust_speed': 32.76,
'wind_speed': 10.08,
})
# ---
# name: test_forecast_daily.1
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-13T12:00:00',
'humidity': 59,
'precipitation': 0.0,
'pressure': 1013.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 234,
'wind_gust_speed': 35.64,
'wind_speed': 14.76,
})
# ---
# name: test_forecast_daily.2
dict({
'cloud_coverage': 100,
'condition': 'fog',
'datetime': '2023-08-07T09:00:00',
'humidity': 100,
'precipitation': 0.0,
'pressure': 992.0,
'temperature': 18.0,
'templow': 18.0,
'wind_bearing': 103,
'wind_gust_speed': 23.76,
'wind_speed': 9.72,
})
# ---
# name: test_forecast_daily.3
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T15:00:00',
'humidity': 89,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 16.0,
'templow': 16.0,
'wind_bearing': 108,
'wind_gust_speed': 31.68,
'wind_speed': 12.24,
})
# ---
# name: test_forecast_service
dict({
'forecast': list([
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T12:00:00',
'humidity': 96,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 18.0,
'templow': 15.0,
'wind_bearing': 114,
'wind_gust_speed': 32.76,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-08T12:00:00',
'humidity': 97,
'precipitation': 10.6,
'pressure': 984.0,
'temperature': 15.0,
'templow': 11.0,
'wind_bearing': 183,
'wind_gust_speed': 27.36,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-09T12:00:00',
'humidity': 95,
'precipitation': 6.3,
'pressure': 1001.0,
'temperature': 12.0,
'templow': 11.0,
'wind_bearing': 166,
'wind_gust_speed': 48.24,
'wind_speed': 18.0,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-10T12:00:00',
'humidity': 75,
'precipitation': 4.8,
'pressure': 1011.0,
'temperature': 14.0,
'templow': 10.0,
'wind_bearing': 174,
'wind_gust_speed': 29.16,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-11T12:00:00',
'humidity': 69,
'precipitation': 0.6,
'pressure': 1015.0,
'temperature': 18.0,
'templow': 12.0,
'wind_bearing': 197,
'wind_gust_speed': 27.36,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-12T12:00:00',
'humidity': 82,
'precipitation': 0.0,
'pressure': 1014.0,
'temperature': 17.0,
'templow': 12.0,
'wind_bearing': 225,
'wind_gust_speed': 28.08,
'wind_speed': 8.64,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-13T12:00:00',
'humidity': 59,
'precipitation': 0.0,
'pressure': 1013.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 234,
'wind_gust_speed': 35.64,
'wind_speed': 14.76,
}),
dict({
'cloud_coverage': 100,
'condition': 'partlycloudy',
'datetime': '2023-08-14T12:00:00',
'humidity': 56,
'precipitation': 0.0,
'pressure': 1015.0,
'temperature': 21.0,
'templow': 14.0,
'wind_bearing': 216,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 88,
'condition': 'partlycloudy',
'datetime': '2023-08-15T12:00:00',
'humidity': 64,
'precipitation': 3.6,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 226,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-16T12:00:00',
'humidity': 61,
'precipitation': 2.4,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 233,
'wind_gust_speed': 33.48,
'wind_speed': 14.04,
}),
]),
})
# ---
# name: test_forecast_service[forecast]
dict({
'weather.smhi_test': dict({
'forecast': list([
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T12:00:00',
'humidity': 96,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 18.0,
'templow': 15.0,
'wind_bearing': 114,
'wind_gust_speed': 32.76,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-08T12:00:00',
'humidity': 97,
'precipitation': 10.6,
'pressure': 984.0,
'temperature': 15.0,
'templow': 11.0,
'wind_bearing': 183,
'wind_gust_speed': 27.36,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-09T12:00:00',
'humidity': 95,
'precipitation': 6.3,
'pressure': 1001.0,
'temperature': 12.0,
'templow': 11.0,
'wind_bearing': 166,
'wind_gust_speed': 48.24,
'wind_speed': 18.0,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-10T12:00:00',
'humidity': 75,
'precipitation': 4.8,
'pressure': 1011.0,
'temperature': 14.0,
'templow': 10.0,
'wind_bearing': 174,
'wind_gust_speed': 29.16,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-11T12:00:00',
'humidity': 69,
'precipitation': 0.6,
'pressure': 1015.0,
'temperature': 18.0,
'templow': 12.0,
'wind_bearing': 197,
'wind_gust_speed': 27.36,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-12T12:00:00',
'humidity': 82,
'precipitation': 0.0,
'pressure': 1014.0,
'temperature': 17.0,
'templow': 12.0,
'wind_bearing': 225,
'wind_gust_speed': 28.08,
'wind_speed': 8.64,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-13T12:00:00',
'humidity': 59,
'precipitation': 0.0,
'pressure': 1013.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 234,
'wind_gust_speed': 35.64,
'wind_speed': 14.76,
}),
dict({
'cloud_coverage': 100,
'condition': 'partlycloudy',
'datetime': '2023-08-14T12:00:00',
'humidity': 56,
'precipitation': 0.0,
'pressure': 1015.0,
'temperature': 21.0,
'templow': 14.0,
'wind_bearing': 216,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 88,
'condition': 'partlycloudy',
'datetime': '2023-08-15T12:00:00',
'humidity': 64,
'precipitation': 3.6,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 226,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-16T12:00:00',
'humidity': 61,
'precipitation': 2.4,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 233,
'wind_gust_speed': 33.48,
'wind_speed': 14.04,
}),
]),
}),
})
# ---
# name: test_forecast_service[get_forecast] # name: test_forecast_service[get_forecast]
dict({ dict({
'forecast': list([ 'forecast': list([
@ -671,138 +337,6 @@
ReadOnlyDict({ ReadOnlyDict({
'attribution': 'Swedish weather institute (SMHI)', 'attribution': 'Swedish weather institute (SMHI)',
'cloud_coverage': 100, 'cloud_coverage': 100,
'forecast': list([
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-07T12:00:00',
'humidity': 96,
'precipitation': 0.0,
'pressure': 991.0,
'temperature': 18.0,
'templow': 15.0,
'wind_bearing': 114,
'wind_gust_speed': 32.76,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-08T12:00:00',
'humidity': 97,
'precipitation': 10.6,
'pressure': 984.0,
'temperature': 15.0,
'templow': 11.0,
'wind_bearing': 183,
'wind_gust_speed': 27.36,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-09T12:00:00',
'humidity': 95,
'precipitation': 6.3,
'pressure': 1001.0,
'temperature': 12.0,
'templow': 11.0,
'wind_bearing': 166,
'wind_gust_speed': 48.24,
'wind_speed': 18.0,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-10T12:00:00',
'humidity': 75,
'precipitation': 4.8,
'pressure': 1011.0,
'temperature': 14.0,
'templow': 10.0,
'wind_bearing': 174,
'wind_gust_speed': 29.16,
'wind_speed': 11.16,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-11T12:00:00',
'humidity': 69,
'precipitation': 0.6,
'pressure': 1015.0,
'temperature': 18.0,
'templow': 12.0,
'wind_bearing': 197,
'wind_gust_speed': 27.36,
'wind_speed': 10.08,
}),
dict({
'cloud_coverage': 100,
'condition': 'cloudy',
'datetime': '2023-08-12T12:00:00',
'humidity': 82,
'precipitation': 0.0,
'pressure': 1014.0,
'temperature': 17.0,
'templow': 12.0,
'wind_bearing': 225,
'wind_gust_speed': 28.08,
'wind_speed': 8.64,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-13T12:00:00',
'humidity': 59,
'precipitation': 0.0,
'pressure': 1013.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 234,
'wind_gust_speed': 35.64,
'wind_speed': 14.76,
}),
dict({
'cloud_coverage': 100,
'condition': 'partlycloudy',
'datetime': '2023-08-14T12:00:00',
'humidity': 56,
'precipitation': 0.0,
'pressure': 1015.0,
'temperature': 21.0,
'templow': 14.0,
'wind_bearing': 216,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 88,
'condition': 'partlycloudy',
'datetime': '2023-08-15T12:00:00',
'humidity': 64,
'precipitation': 3.6,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 226,
'wind_gust_speed': 33.12,
'wind_speed': 13.68,
}),
dict({
'cloud_coverage': 75,
'condition': 'partlycloudy',
'datetime': '2023-08-16T12:00:00',
'humidity': 61,
'precipitation': 2.4,
'pressure': 1014.0,
'temperature': 20.0,
'templow': 14.0,
'wind_bearing': 233,
'wind_gust_speed': 33.48,
'wind_speed': 14.04,
}),
]),
'friendly_name': 'test', 'friendly_name': 'test',
'humidity': 100, 'humidity': 100,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>, 'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
@ -820,18 +354,3 @@
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>, 'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
}) })
# --- # ---
# name: test_setup_hass.1
dict({
'cloud_coverage': 100,
'condition': 'rainy',
'datetime': '2023-08-08T12:00:00',
'humidity': 97,
'precipitation': 10.6,
'pressure': 984.0,
'temperature': 15.0,
'templow': 11.0,
'wind_bearing': 183,
'wind_gust_speed': 27.36,
'wind_speed': 11.16,
})
# ---

View file

@ -10,7 +10,6 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.smhi.const import ATTR_SMHI_THUNDER_PROBABILITY from homeassistant.components.smhi.const import ATTR_SMHI_THUNDER_PROBABILITY
from homeassistant.components.smhi.weather import CONDITION_CLASSES, RETRY_TIMEOUT from homeassistant.components.smhi.weather import CONDITION_CLASSES, RETRY_TIMEOUT
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_FORECAST_CONDITION, ATTR_FORECAST_CONDITION,
ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_HUMIDITY,
ATTR_WEATHER_PRESSURE, ATTR_WEATHER_PRESSURE,
@ -65,10 +64,6 @@ async def test_setup_hass(
assert state assert state
assert state.state == "fog" assert state.state == "fog"
assert state.attributes == snapshot assert state.attributes == snapshot
assert len(state.attributes["forecast"]) == 10
forecast = state.attributes["forecast"][1]
assert forecast == snapshot
async def test_properties_no_data(hass: HomeAssistant) -> None: async def test_properties_no_data(hass: HomeAssistant) -> None:
@ -95,7 +90,6 @@ async def test_properties_no_data(hass: HomeAssistant) -> None:
assert ATTR_WEATHER_VISIBILITY not in state.attributes assert ATTR_WEATHER_VISIBILITY not in state.attributes
assert ATTR_WEATHER_WIND_SPEED not in state.attributes assert ATTR_WEATHER_WIND_SPEED not in state.attributes
assert ATTR_WEATHER_WIND_BEARING not in state.attributes assert ATTR_WEATHER_WIND_BEARING not in state.attributes
assert ATTR_FORECAST not in state.attributes
assert ATTR_WEATHER_CLOUD_COVERAGE not in state.attributes assert ATTR_WEATHER_CLOUD_COVERAGE not in state.attributes
assert ATTR_SMHI_THUNDER_PROBABILITY not in state.attributes assert ATTR_SMHI_THUNDER_PROBABILITY not in state.attributes
assert ATTR_WEATHER_WIND_GUST_SPEED not in state.attributes assert ATTR_WEATHER_WIND_GUST_SPEED not in state.attributes
@ -183,10 +177,16 @@ async def test_properties_unknown_symbol(hass: HomeAssistant) -> None:
assert state assert state
assert state.name == "test" assert state.name == "test"
assert state.state == STATE_UNKNOWN assert state.state == STATE_UNKNOWN
assert ATTR_FORECAST in state.attributes response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
{"entity_id": ENTITY_ID, "type": "daily"},
blocking=True,
return_response=True,
)
assert all( assert all(
forecast[ATTR_FORECAST_CONDITION] is None forecast[ATTR_FORECAST_CONDITION] is None
for forecast in state.attributes[ATTR_FORECAST] for forecast in response[ENTITY_ID]["forecast"]
) )

View file

@ -55,22 +55,12 @@
# name: test_forecasts[config0-1-weather-get_forecast] # name: test_forecasts[config0-1-weather-get_forecast]
dict({ dict({
'forecast': list([ 'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]), ]),
}) })
# --- # ---
# name: test_forecasts[config0-1-weather-get_forecast].1 # name: test_forecasts[config0-1-weather-get_forecast].1
dict({ dict({
'forecast': list([ 'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]), ]),
}) })
# --- # ---
@ -89,11 +79,6 @@
# name: test_forecasts[config0-1-weather-get_forecast].3 # name: test_forecasts[config0-1-weather-get_forecast].3
dict({ dict({
'forecast': list([ 'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 16.9,
}),
]), ]),
}) })
# --- # ---
@ -101,11 +86,6 @@
dict({ dict({
'weather.forecast': dict({ 'weather.forecast': dict({
'forecast': list([ 'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]), ]),
}), }),
}) })
@ -114,11 +94,6 @@
dict({ dict({
'weather.forecast': dict({ 'weather.forecast': dict({
'forecast': list([ 'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]), ]),
}), }),
}) })
@ -141,11 +116,6 @@
dict({ dict({
'weather.forecast': dict({ 'weather.forecast': dict({
'forecast': list([ 'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 16.9,
}),
]), ]),
}), }),
}) })

View file

@ -6,7 +6,6 @@ import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_APPARENT_TEMPERATURE,
ATTR_WEATHER_CLOUD_COVERAGE, ATTR_WEATHER_CLOUD_COVERAGE,
ATTR_WEATHER_DEW_POINT, ATTR_WEATHER_DEW_POINT,
@ -36,6 +35,8 @@ from tests.common import (
mock_restore_cache_with_extra_data, mock_restore_cache_with_extra_data,
) )
ATTR_FORECAST = "forecast"
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)]) @pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -49,7 +50,6 @@ from tests.common import (
"name": "test", "name": "test",
"attribution_template": "{{ states('sensor.attribution') }}", "attribution_template": "{{ states('sensor.attribution') }}",
"condition_template": "sunny", "condition_template": "sunny",
"forecast_template": "{{ states.weather.demo.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}", "temperature_template": "{{ states('sensor.temperature') | float }}",
"humidity_template": "{{ states('sensor.humidity') | int }}", "humidity_template": "{{ states('sensor.humidity') | int }}",
"pressure_template": "{{ states('sensor.pressure') }}", "pressure_template": "{{ states('sensor.pressure') }}",
@ -111,7 +111,6 @@ async def test_template_state_text(hass: HomeAssistant, start_ha) -> None:
"platform": "template", "platform": "template",
"name": "forecast", "name": "forecast",
"condition_template": "sunny", "condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_daily_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_daily_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_hourly_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_hourly_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}", "forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}",
@ -238,7 +237,6 @@ async def test_forecasts(
"platform": "template", "platform": "template",
"name": "forecast", "name": "forecast",
"condition_template": "sunny", "condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_daily_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_daily_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_hourly_template": "{{ states.weather.forecast_hourly.attributes.forecast }}", "forecast_hourly_template": "{{ states.weather.forecast_hourly.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}", "temperature_template": "{{ states('sensor.temperature') | float }}",
@ -323,7 +321,6 @@ async def test_forecast_invalid(
"platform": "template", "platform": "template",
"name": "forecast", "name": "forecast",
"condition_template": "sunny", "condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}", "forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}", "temperature_template": "{{ states('sensor.temperature') | float }}",
"humidity_template": "{{ states('sensor.humidity') | int }}", "humidity_template": "{{ states('sensor.humidity') | int }}",
@ -393,7 +390,6 @@ async def test_forecast_invalid_is_daytime_missing_in_twice_daily(
"platform": "template", "platform": "template",
"name": "forecast", "name": "forecast",
"condition_template": "sunny", "condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}", "forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}", "temperature_template": "{{ states('sensor.temperature') | float }}",
"humidity_template": "{{ states('sensor.humidity') | int }}", "humidity_template": "{{ states('sensor.humidity') | int }}",
@ -463,7 +459,6 @@ async def test_forecast_invalid_datetime_missing(
"platform": "template", "platform": "template",
"name": "forecast", "name": "forecast",
"condition_template": "sunny", "condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_daily_template": "{{ states.weather.forecast_daily.attributes.forecast }}", "forecast_daily_template": "{{ states.weather.forecast_daily.attributes.forecast }}",
"forecast_hourly_template": "{{ states.weather.forecast_hourly.attributes.forecast }}", "forecast_hourly_template": "{{ states.weather.forecast_hourly.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}", "temperature_template": "{{ states('sensor.temperature') | float }}",
@ -728,7 +723,6 @@ async def test_trigger_action(
"cloud_coverage_template": "{{ my_variable + 1 }}", "cloud_coverage_template": "{{ my_variable + 1 }}",
"dew_point_template": "{{ my_variable + 1 }}", "dew_point_template": "{{ my_variable + 1 }}",
"apparent_temperature_template": "{{ my_variable + 1 }}", "apparent_temperature_template": "{{ my_variable + 1 }}",
"forecast_template": "{{ var_forecast_daily }}",
"forecast_daily_template": "{{ var_forecast_daily }}", "forecast_daily_template": "{{ var_forecast_daily }}",
"forecast_hourly_template": "{{ var_forecast_hourly }}", "forecast_hourly_template": "{{ var_forecast_hourly }}",
"forecast_twice_daily_template": "{{ var_forecast_twice_daily }}", "forecast_twice_daily_template": "{{ var_forecast_twice_daily }}",

View file

@ -23,17 +23,6 @@ from homeassistant.components.tomorrowio.const import (
) )
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_SUNNY, ATTR_CONDITION_SUNNY,
ATTR_FORECAST,
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_DEW_POINT,
ATTR_FORECAST_HUMIDITY,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_WIND_SPEED,
ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_HUMIDITY,
ATTR_WEATHER_OZONE, ATTR_WEATHER_OZONE,
ATTR_WEATHER_PRECIPITATION_UNIT, ATTR_WEATHER_PRECIPITATION_UNIT,
@ -216,19 +205,6 @@ async def test_v4_weather(hass: HomeAssistant, tomorrowio_config_entry_update) -
assert weather_state.state == ATTR_CONDITION_SUNNY assert weather_state.state == ATTR_CONDITION_SUNNY
assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION
assert len(weather_state.attributes[ATTR_FORECAST]) == 14
assert weather_state.attributes[ATTR_FORECAST][0] == {
ATTR_FORECAST_CONDITION: ATTR_CONDITION_SUNNY,
ATTR_FORECAST_TIME: "2021-03-07T11:00:00+00:00",
ATTR_FORECAST_PRECIPITATION: 0,
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0,
ATTR_FORECAST_TEMP: 45.9,
ATTR_FORECAST_TEMP_LOW: 26.1,
ATTR_FORECAST_DEW_POINT: 12.8,
ATTR_FORECAST_HUMIDITY: 58,
ATTR_FORECAST_WIND_BEARING: 239.6,
ATTR_FORECAST_WIND_SPEED: 34.16, # 9.49 m/s -> km/h
}
assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "Tomorrow.io Daily" assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "Tomorrow.io Daily"
assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23 assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23
assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53 assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53
@ -249,19 +225,6 @@ async def test_v4_weather_legacy_entities(hass: HomeAssistant) -> None:
weather_state = await _setup_legacy(hass, API_V4_ENTRY_DATA) weather_state = await _setup_legacy(hass, API_V4_ENTRY_DATA)
assert weather_state.state == ATTR_CONDITION_SUNNY assert weather_state.state == ATTR_CONDITION_SUNNY
assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION
assert len(weather_state.attributes[ATTR_FORECAST]) == 14
assert weather_state.attributes[ATTR_FORECAST][0] == {
ATTR_FORECAST_CONDITION: ATTR_CONDITION_SUNNY,
ATTR_FORECAST_TIME: "2021-03-07T11:00:00+00:00",
ATTR_FORECAST_DEW_POINT: 12.8,
ATTR_FORECAST_HUMIDITY: 58,
ATTR_FORECAST_PRECIPITATION: 0,
ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0,
ATTR_FORECAST_TEMP: 45.9,
ATTR_FORECAST_TEMP_LOW: 26.1,
ATTR_FORECAST_WIND_BEARING: 239.6,
ATTR_FORECAST_WIND_SPEED: 34.16, # 9.49 m/s -> km/h
}
assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "Tomorrow.io Daily" assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "Tomorrow.io Daily"
assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23 assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23
assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53 assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53

View file

@ -7,17 +7,6 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_SUNNY, ATTR_CONDITION_SUNNY,
ATTR_FORECAST,
ATTR_FORECAST_APPARENT_TEMP,
ATTR_FORECAST_DEW_POINT,
ATTR_FORECAST_HUMIDITY,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_PRESSURE,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_UV_INDEX,
ATTR_FORECAST_WIND_GUST_SPEED,
ATTR_FORECAST_WIND_SPEED,
ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_APPARENT_TEMPERATURE,
ATTR_WEATHER_OZONE, ATTR_WEATHER_OZONE,
ATTR_WEATHER_PRECIPITATION_UNIT, ATTR_WEATHER_PRECIPITATION_UNIT,
@ -79,6 +68,7 @@ class MockWeatherEntity(WeatherEntity):
def __init__(self) -> None: def __init__(self) -> None:
"""Initiate Entity.""" """Initiate Entity."""
super().__init__() super().__init__()
self._attr_precision = PRECISION_TENTHS
self._attr_condition = ATTR_CONDITION_SUNNY self._attr_condition = ATTR_CONDITION_SUNNY
self._attr_native_precipitation_unit = UnitOfLength.MILLIMETERS self._attr_native_precipitation_unit = UnitOfLength.MILLIMETERS
self._attr_native_pressure = 10 self._attr_native_pressure = 10
@ -92,14 +82,6 @@ class MockWeatherEntity(WeatherEntity):
self._attr_native_wind_gust_speed = 10 self._attr_native_wind_gust_speed = 10
self._attr_native_wind_speed = 3 self._attr_native_wind_speed = 3
self._attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND self._attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
self._attr_forecast = [
Forecast(
datetime=datetime(2022, 6, 20, 00, 00, 00, tzinfo=dt_util.UTC),
native_precipitation=1,
native_temperature=20,
native_dew_point=2,
)
]
self._attr_forecast_twice_daily = [ self._attr_forecast_twice_daily = [
Forecast( Forecast(
datetime=datetime(2022, 6, 20, 8, 00, 00, tzinfo=dt_util.UTC), datetime=datetime(2022, 6, 20, 8, 00, 00, tzinfo=dt_util.UTC),
@ -141,14 +123,6 @@ async def test_temperature(
dew_point_native_value, native_unit, state_unit dew_point_native_value, native_unit, state_unit
) )
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = { kwargs = {
"native_temperature": native_value, "native_temperature": native_value,
"native_temperature_unit": native_unit, "native_temperature_unit": native_unit,
@ -156,10 +130,9 @@ async def test_temperature(
"native_dew_point": dew_point_native_value, "native_dew_point": dew_point_native_value,
} }
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast_daily = state.attributes[ATTR_FORECAST][0]
expected = state_value expected = state_value
apparent_expected = apparent_state_value apparent_expected = apparent_state_value
@ -174,20 +147,6 @@ async def test_temperature(
dew_point_expected, rel=0.1 dew_point_expected, rel=0.1
) )
assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit
assert float(forecast_daily[ATTR_FORECAST_TEMP]) == pytest.approx(expected, rel=0.1)
assert float(forecast_daily[ATTR_FORECAST_APPARENT_TEMP]) == pytest.approx(
apparent_expected, rel=0.1
)
assert float(forecast_daily[ATTR_FORECAST_DEW_POINT]) == pytest.approx(
dew_point_expected, rel=0.1
)
assert float(forecast_daily[ATTR_FORECAST_TEMP_LOW]) == pytest.approx(
expected, rel=0.1
)
assert float(forecast_daily[ATTR_FORECAST_TEMP]) == pytest.approx(expected, rel=0.1)
assert float(forecast_daily[ATTR_FORECAST_TEMP_LOW]) == pytest.approx(
expected, rel=0.1
)
@pytest.mark.parametrize("native_unit", [None]) @pytest.mark.parametrize("native_unit", [None])
@ -214,14 +173,6 @@ async def test_temperature_no_unit(
dew_point_state_value = dew_point_native_value dew_point_state_value = dew_point_native_value
apparent_temp_state_value = apparent_temp_native_value apparent_temp_state_value = apparent_temp_native_value
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = { kwargs = {
"native_temperature": native_value, "native_temperature": native_value,
"native_temperature_unit": native_unit, "native_temperature_unit": native_unit,
@ -229,10 +180,9 @@ async def test_temperature_no_unit(
"native_apparent_temperature": apparent_temp_native_value, "native_apparent_temperature": apparent_temp_native_value,
} }
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value expected = state_value
dew_point_expected = dew_point_state_value dew_point_expected = dew_point_state_value
@ -247,14 +197,6 @@ async def test_temperature_no_unit(
expected_apparent_temp, rel=0.1 expected_apparent_temp, rel=0.1
) )
assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit
assert float(forecast[ATTR_FORECAST_TEMP]) == pytest.approx(expected, rel=0.1)
assert float(forecast[ATTR_FORECAST_DEW_POINT]) == pytest.approx(
dew_point_expected, rel=0.1
)
assert float(forecast[ATTR_FORECAST_TEMP_LOW]) == pytest.approx(expected, rel=0.1)
assert float(forecast[ATTR_FORECAST_APPARENT_TEMP]) == pytest.approx(
expected_apparent_temp, rel=0.1
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -276,26 +218,16 @@ async def test_pressure(
native_value = 30 native_value = 30
state_value = PressureConverter.convert(native_value, native_unit, state_unit) state_value = PressureConverter.convert(native_value, native_unit, state_unit)
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {"native_pressure": native_value, "native_pressure_unit": native_unit} kwargs = {"native_pressure": native_value, "native_pressure_unit": native_unit}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value expected = state_value
assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == pytest.approx( assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == pytest.approx(
expected, rel=1e-2 expected, rel=1e-2
) )
assert float(forecast[ATTR_FORECAST_PRESSURE]) == pytest.approx(expected, rel=1e-2)
@pytest.mark.parametrize("native_unit", [None]) @pytest.mark.parametrize("native_unit", [None])
@ -315,26 +247,16 @@ async def test_pressure_no_unit(
native_value = 30 native_value = 30
state_value = native_value state_value = native_value
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {"native_pressure": native_value, "native_pressure_unit": native_unit} kwargs = {"native_pressure": native_value, "native_pressure_unit": native_unit}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value expected = state_value
assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == pytest.approx( assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == pytest.approx(
expected, rel=1e-2 expected, rel=1e-2
) )
assert float(forecast[ATTR_FORECAST_PRESSURE]) == pytest.approx(expected, rel=1e-2)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -364,28 +286,16 @@ async def test_wind_speed(
native_value = 10 native_value = 10
state_value = SpeedConverter.convert(native_value, native_unit, state_unit) state_value = SpeedConverter.convert(native_value, native_unit, state_unit)
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {"native_wind_speed": native_value, "native_wind_speed_unit": native_unit} kwargs = {"native_wind_speed": native_value, "native_wind_speed_unit": native_unit}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value expected = state_value
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx( assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx(
expected, rel=1e-2 expected, rel=1e-2
) )
assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == pytest.approx(
expected, rel=1e-2
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -415,31 +325,19 @@ async def test_wind_gust_speed(
native_value = 10 native_value = 10
state_value = SpeedConverter.convert(native_value, native_unit, state_unit) state_value = SpeedConverter.convert(native_value, native_unit, state_unit)
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = { kwargs = {
"native_wind_gust_speed": native_value, "native_wind_gust_speed": native_value,
"native_wind_speed_unit": native_unit, "native_wind_speed_unit": native_unit,
} }
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value expected = state_value
assert float(state.attributes[ATTR_WEATHER_WIND_GUST_SPEED]) == pytest.approx( assert float(state.attributes[ATTR_WEATHER_WIND_GUST_SPEED]) == pytest.approx(
expected, rel=1e-2 expected, rel=1e-2
) )
assert float(forecast[ATTR_FORECAST_WIND_GUST_SPEED]) == pytest.approx(
expected, rel=1e-2
)
@pytest.mark.parametrize("native_unit", [None]) @pytest.mark.parametrize("native_unit", [None])
@ -462,194 +360,16 @@ async def test_wind_speed_no_unit(
native_value = 10 native_value = 10
state_value = native_value state_value = native_value
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {"native_wind_speed": native_value, "native_wind_speed_unit": native_unit} kwargs = {"native_wind_speed": native_value, "native_wind_speed_unit": native_unit}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value expected = state_value
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx( assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx(
expected, rel=1e-2 expected, rel=1e-2
) )
assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == pytest.approx(
expected, rel=1e-2
)
@pytest.mark.parametrize("native_unit", [UnitOfLength.MILES, UnitOfLength.KILOMETERS])
@pytest.mark.parametrize(
("state_unit", "unit_system"),
[
(UnitOfLength.KILOMETERS, METRIC_SYSTEM),
(UnitOfLength.MILES, US_CUSTOMARY_SYSTEM),
],
)
async def test_visibility(
hass: HomeAssistant,
config_flow_fixture: None,
native_unit: str,
state_unit: str,
unit_system,
) -> None:
"""Test visibility."""
hass.config.units = unit_system
native_value = 10
state_value = DistanceConverter.convert(native_value, native_unit, state_unit)
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {"native_visibility": native_value, "native_visibility_unit": native_unit}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
state = hass.states.get(entity0.entity_id)
expected = state_value
assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == pytest.approx(
expected, rel=1e-2
)
@pytest.mark.parametrize("native_unit", [None])
@pytest.mark.parametrize(
("state_unit", "unit_system"),
[
(UnitOfLength.KILOMETERS, METRIC_SYSTEM),
(UnitOfLength.MILES, US_CUSTOMARY_SYSTEM),
],
)
async def test_visibility_no_unit(
hass: HomeAssistant,
config_flow_fixture: None,
native_unit: str,
state_unit: str,
unit_system,
) -> None:
"""Test visibility when the entity does not declare a native unit."""
hass.config.units = unit_system
native_value = 10
state_value = native_value
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {"native_visibility": native_value, "native_visibility_unit": native_unit}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
state = hass.states.get(entity0.entity_id)
expected = state_value
assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == pytest.approx(
expected, rel=1e-2
)
@pytest.mark.parametrize("native_unit", [UnitOfLength.INCHES, UnitOfLength.MILLIMETERS])
@pytest.mark.parametrize(
("state_unit", "unit_system"),
[
(UnitOfLength.MILLIMETERS, METRIC_SYSTEM),
(UnitOfLength.INCHES, US_CUSTOMARY_SYSTEM),
],
)
async def test_precipitation(
hass: HomeAssistant,
config_flow_fixture: None,
native_unit: str,
state_unit: str,
unit_system,
) -> None:
"""Test precipitation."""
hass.config.units = unit_system
native_value = 30
state_value = DistanceConverter.convert(native_value, native_unit, state_unit)
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {
"native_precipitation": native_value,
"native_precipitation_unit": native_unit,
}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value
assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == pytest.approx(
expected, rel=1e-2
)
@pytest.mark.parametrize("native_unit", [None])
@pytest.mark.parametrize(
("state_unit", "unit_system"),
[
(UnitOfLength.MILLIMETERS, METRIC_SYSTEM),
(UnitOfLength.INCHES, US_CUSTOMARY_SYSTEM),
],
)
async def test_precipitation_no_unit(
hass: HomeAssistant,
config_flow_fixture: None,
native_unit: str,
state_unit: str,
unit_system,
) -> None:
"""Test precipitation when the entity does not declare a native unit."""
hass.config.units = unit_system
native_value = 30
state_value = native_value
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {
"native_precipitation": native_value,
"native_precipitation_unit": native_unit,
}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value
assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == pytest.approx(
expected, rel=1e-2
)
async def test_wind_bearing_ozone_and_cloud_coverage_and_uv_index( async def test_wind_bearing_ozone_and_cloud_coverage_and_uv_index(
@ -662,14 +382,6 @@ async def test_wind_bearing_ozone_and_cloud_coverage_and_uv_index(
cloud_coverage = 75 cloud_coverage = 75
uv_index = 1.2 uv_index = 1.2
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = { kwargs = {
"wind_bearing": wind_bearing_value, "wind_bearing": wind_bearing_value,
"ozone": ozone_value, "ozone": ozone_value,
@ -677,15 +389,13 @@ async def test_wind_bearing_ozone_and_cloud_coverage_and_uv_index(
"uv_index": uv_index, "uv_index": uv_index,
} }
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
assert float(state.attributes[ATTR_WEATHER_WIND_BEARING]) == 180 assert float(state.attributes[ATTR_WEATHER_WIND_BEARING]) == 180
assert float(state.attributes[ATTR_WEATHER_OZONE]) == 10 assert float(state.attributes[ATTR_WEATHER_OZONE]) == 10
assert float(state.attributes[ATTR_WEATHER_CLOUD_COVERAGE]) == 75 assert float(state.attributes[ATTR_WEATHER_CLOUD_COVERAGE]) == 75
assert float(state.attributes[ATTR_WEATHER_UV_INDEX]) == 1.2 assert float(state.attributes[ATTR_WEATHER_UV_INDEX]) == 1.2
assert float(forecast[ATTR_FORECAST_UV_INDEX]) == 1.2
async def test_humidity( async def test_humidity(
@ -695,55 +405,12 @@ async def test_humidity(
"""Test humidity.""" """Test humidity."""
humidity_value = 80.2 humidity_value = 80.2
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {"humidity": humidity_value} kwargs = {"humidity": humidity_value}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
assert float(state.attributes[ATTR_WEATHER_HUMIDITY]) == 80 assert float(state.attributes[ATTR_WEATHER_HUMIDITY]) == 80
assert float(forecast[ATTR_FORECAST_HUMIDITY]) == 80
async def test_none_forecast(
hass: HomeAssistant,
config_flow_fixture: None,
) -> None:
"""Test that conversion with None values succeeds."""
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {
"native_pressure": None,
"native_pressure_unit": UnitOfPressure.INHG,
"native_wind_speed": None,
"native_wind_speed_unit": UnitOfSpeed.METERS_PER_SECOND,
"native_precipitation": None,
"native_precipitation_unit": UnitOfLength.MILLIMETERS,
}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
assert forecast.get(ATTR_FORECAST_PRESSURE) is None
assert forecast.get(ATTR_FORECAST_WIND_SPEED) is None
assert forecast.get(ATTR_FORECAST_PRECIPITATION) is None
async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> None: async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> None:
@ -773,14 +440,6 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N
entity_registry.async_update_entity_options(entry.entity_id, "weather", set_options) entity_registry.async_update_entity_options(entry.entity_id, "weather", set_options)
await hass.async_block_till_done() await hass.async_block_till_done()
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = { kwargs = {
"native_temperature": temperature_value, "native_temperature": temperature_value,
"native_temperature_unit": temperature_unit, "native_temperature_unit": temperature_unit,
@ -796,10 +455,9 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N
"unique_id": "very_unique", "unique_id": "very_unique",
} }
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id) state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected_wind_speed = round( expected_wind_speed = round(
SpeedConverter.convert( SpeedConverter.convert(
@ -820,12 +478,6 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N
), ),
ROUNDING_PRECISION, ROUNDING_PRECISION,
) )
expected_precipitation = round(
DistanceConverter.convert(
precipitation_value, precipitation_unit, UnitOfLength.INCHES
),
ROUNDING_PRECISION,
)
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx( assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx(
expected_wind_speed expected_wind_speed
@ -839,9 +491,6 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N
assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == pytest.approx( assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == pytest.approx(
expected_visibility expected_visibility
) )
assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == pytest.approx(
expected_precipitation, rel=1e-2
)
assert ( assert (
state.attributes[ATTR_WEATHER_PRECIPITATION_UNIT] state.attributes[ATTR_WEATHER_PRECIPITATION_UNIT]
@ -925,14 +574,6 @@ async def test_forecast_twice_daily_missing_is_daytime(
) -> None: ) -> None:
"""Test forecast_twice_daily missing mandatory attribute is_daytime.""" """Test forecast_twice_daily missing mandatory attribute is_daytime."""
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = { kwargs = {
"native_temperature": 38, "native_temperature": 38,
"native_temperature_unit": UnitOfTemperature.CELSIUS, "native_temperature_unit": UnitOfTemperature.CELSIUS,
@ -940,7 +581,7 @@ async def test_forecast_twice_daily_missing_is_daytime(
"supported_features": WeatherEntityFeature.FORECAST_TWICE_DAILY, "supported_features": WeatherEntityFeature.FORECAST_TWICE_DAILY,
} }
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
@ -1147,155 +788,6 @@ async def test_get_forecast_unsupported(
ISSUE_TRACKER = "https://blablabla.com" ISSUE_TRACKER = "https://blablabla.com"
@pytest.mark.parametrize(
("manifest_extra", "translation_key", "translation_placeholders_extra", "report"),
[
(
{},
"deprecated_weather_forecast_no_url",
{},
"report it to the author of the 'test' custom integration",
),
(
{"issue_tracker": ISSUE_TRACKER},
"deprecated_weather_forecast_url",
{"issue_tracker": ISSUE_TRACKER},
"create a bug report at https://blablabla.com",
),
],
)
async def test_issue_forecast_property_deprecated(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
config_flow_fixture: None,
manifest_extra: dict[str, str],
translation_key: str,
translation_placeholders_extra: dict[str, str],
report: str,
) -> None:
"""Test the issue is raised on deprecated forecast attributes."""
class MockWeatherMockLegacyForecastOnly(MockWeatherTest):
"""Mock weather class with mocked legacy forecast."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
# Fake that the class belongs to a custom integration
MockWeatherMockLegacyForecastOnly.__module__ = "custom_components.test.weather"
kwargs = {
"native_temperature": 38,
"native_temperature_unit": UnitOfTemperature.CELSIUS,
}
weather_entity = await create_entity(
hass, MockWeatherMockLegacyForecastOnly, manifest_extra, **kwargs
)
assert weather_entity.state == ATTR_CONDITION_SUNNY
issues = ir.async_get(hass)
issue = issues.async_get_issue("weather", "deprecated_weather_forecast_test")
assert issue
assert issue.issue_domain == "test"
assert issue.issue_id == "deprecated_weather_forecast_test"
assert issue.translation_key == translation_key
assert (
issue.translation_placeholders
== {"platform": "test"} | translation_placeholders_extra
)
assert (
"test::MockWeatherMockLegacyForecastOnly implements the `forecast` property or "
"sets `self._attr_forecast` in a subclass of WeatherEntity, this is deprecated "
f"and will be unsupported from Home Assistant 2024.3. Please {report}"
) in caplog.text
async def test_issue_forecast_attr_deprecated(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
config_flow_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the issue is raised on deprecated forecast attributes."""
class MockWeatherMockLegacyForecast(MockWeatherTest):
"""Mock weather class with legacy forecast."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {
"native_temperature": 38,
"native_temperature_unit": UnitOfTemperature.CELSIUS,
}
# Fake that the class belongs to a custom integration
MockWeatherMockLegacyForecast.__module__ = "custom_components.test.weather"
weather_entity = await create_entity(
hass, MockWeatherMockLegacyForecast, None, **kwargs
)
assert weather_entity.state == ATTR_CONDITION_SUNNY
issue = issue_registry.async_get_issue(
"weather", "deprecated_weather_forecast_test"
)
assert issue
assert issue.issue_domain == "test"
assert issue.issue_id == "deprecated_weather_forecast_test"
assert issue.translation_key == "deprecated_weather_forecast_no_url"
assert issue.translation_placeholders == {"platform": "test"}
assert (
"test::MockWeatherMockLegacyForecast implements the `forecast` property or "
"sets `self._attr_forecast` in a subclass of WeatherEntity, this is deprecated "
"and will be unsupported from Home Assistant 2024.3. Please report it to the "
"author of the 'test' custom integration"
) in caplog.text
async def test_issue_forecast_deprecated_no_logging(
hass: HomeAssistant,
config_flow_fixture: None,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the no issue is raised on deprecated forecast attributes if new methods exist."""
class MockWeatherMockForecast(MockWeatherTest):
"""Mock weather class with mocked new method and legacy forecast."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
async def async_forecast_daily(self) -> list[Forecast] | None:
"""Return the forecast_daily."""
return self.forecast_list
kwargs = {
"native_temperature": 38,
"native_temperature_unit": UnitOfTemperature.CELSIUS,
}
weather_entity = await create_entity(hass, MockWeatherMockForecast, None, **kwargs)
assert weather_entity.state == ATTR_CONDITION_SUNNY
assert "Setting up test.weather" in caplog.text
assert (
"custom_components.test_weather.weather::weather.test is using a forecast attribute on an instance of WeatherEntity"
not in caplog.text
)
async def test_issue_deprecated_service_weather_get_forecast( async def test_issue_deprecated_service_weather_get_forecast(
hass: HomeAssistant, hass: HomeAssistant,
issue_registry: ir.IssueRegistry, issue_registry: ir.IssueRegistry,

View file

@ -1,59 +0,0 @@
"""The tests for weather recorder."""
from __future__ import annotations
from datetime import timedelta
from homeassistant.components.recorder import Recorder
from homeassistant.components.recorder.history import get_significant_states
from homeassistant.components.weather import ATTR_FORECAST, Forecast
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from homeassistant.util.unit_system import METRIC_SYSTEM
from . import MockWeatherTest, create_entity
from tests.common import async_fire_time_changed
from tests.components.recorder.common import async_wait_recording_done
async def test_exclude_attributes(
recorder_mock: Recorder,
hass: HomeAssistant,
config_flow_fixture: None,
) -> None:
"""Test weather attributes to be excluded."""
now = dt_util.utcnow()
class MockWeatherMockForecast(MockWeatherTest):
"""Mock weather class with mocked legacy forecast."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {
"native_temperature": 38,
"native_temperature_unit": UnitOfTemperature.CELSIUS,
}
weather_entity = await create_entity(hass, MockWeatherMockForecast, None, **kwargs)
hass.config.units = METRIC_SYSTEM
await hass.async_block_till_done()
state = hass.states.get(weather_entity.entity_id)
assert state.attributes[ATTR_FORECAST]
await hass.async_block_till_done()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
await hass.async_block_till_done()
await async_wait_recording_done(hass)
states = await hass.async_add_executor_job(
get_significant_states, hass, now, None, hass.states.async_entity_ids()
)
assert len(states) >= 1
for entity_states in states.values():
for state in entity_states:
assert ATTR_FORECAST not in state.attributes

View file

@ -168,11 +168,6 @@ class MockWeatherMockForecast(MockWeather):
} }
] ]
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
async def async_forecast_daily(self) -> list[Forecast] | None: async def async_forecast_daily(self) -> list[Forecast] | None:
"""Return the forecast_daily.""" """Return the forecast_daily."""
return self.forecast_list return self.forecast_list