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/coordinator.py
homeassistant/components/openuv/sensor.py
homeassistant/components/openweathermap/__init__.py
homeassistant/components/openweathermap/sensor.py
homeassistant/components/openweathermap/weather.py
homeassistant/components/openweathermap/weather_update_coordinator.py
homeassistant/components/opnsense/__init__.py
homeassistant/components/opower/__init__.py

View File

@ -146,9 +146,9 @@ class AccuWeatherEntity(
"""Return the UV index."""
return cast(float, self.coordinator.data["UVIndex"])
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
@callback
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""
if not self.coordinator.forecast:
return None
# 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]
]
@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 = (
f"{coordinates[CONF_LATITUDE]:2.6f}{coordinates[CONF_LONGITUDE]:2.6f}"
)
self._forecast: list | None = None
@callback
def data_updated(self, data: BrData) -> None:
"""Update data."""
self._attr_attribution = data.attribution
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_name = (
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:
"""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 None
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
return self._forecast()
async def async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""
return self._forecast()

View File

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

View File

@ -205,13 +205,6 @@ class IPMAWeather(WeatherEntity, IPMADevice):
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(
self,
forecast_type: Literal["daily", "hourly"],

View File

@ -61,29 +61,6 @@ async def async_setup_entry(
"""Set up the Demo config entry."""
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(
"Legacy + daily weather",
"Sunshine",
@ -103,15 +80,6 @@ async def async_setup_entry(
[ATTR_CONDITION_RAINY, 15, 18, 7, 0],
[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,
),
@ -125,7 +93,6 @@ async def async_setup_entry(
UnitOfTemperature.FAHRENHEIT,
UnitOfPressure.INHG,
UnitOfSpeed.MILES_PER_HOUR,
None,
[
[ATTR_CONDITION_SNOWY, 2, -10, -15, 60],
[ATTR_CONDITION_PARTLYCLOUDY, 1, -13, -14, 25],
@ -156,7 +123,6 @@ async def async_setup_entry(
UnitOfTemperature.CELSIUS,
UnitOfPressure.HPA,
UnitOfSpeed.METERS_PER_SECOND,
None,
[
[ATTR_CONDITION_RAINY, 1, 22, 15, 60],
[ATTR_CONDITION_RAINY, 5, 19, 8, 30],
@ -196,7 +162,6 @@ async def async_setup_entry(
UnitOfPressure.HPA,
UnitOfSpeed.METERS_PER_SECOND,
None,
None,
[
[ATTR_CONDITION_CLOUDY, 1, 22, 15, 60],
[ATTR_CONDITION_CLOUDY, 5, 19, 8, 30],
@ -226,7 +191,6 @@ async def async_setup_entry(
UnitOfTemperature.CELSIUS,
UnitOfPressure.HPA,
UnitOfSpeed.METERS_PER_SECOND,
None,
[
[ATTR_CONDITION_RAINY, 1, 22, 15, 60],
[ATTR_CONDITION_RAINY, 5, 19, 8, 30],
@ -268,7 +232,6 @@ class DemoWeather(WeatherEntity):
temperature_unit: str,
pressure_unit: str,
wind_speed_unit: str,
forecast: list[list] | None,
forecast_daily: list[list] | None,
forecast_hourly: list[list] | None,
forecast_twice_daily: list[list] | None,
@ -284,7 +247,6 @@ class DemoWeather(WeatherEntity):
self._native_pressure_unit = pressure_unit
self._native_wind_speed = wind_speed
self._native_wind_speed_unit = wind_speed_unit
self._forecast = forecast
self._forecast_daily = forecast_daily
self._forecast_hourly = forecast_hourly
self._forecast_twice_daily = forecast_twice_daily
@ -360,28 +322,6 @@ class DemoWeather(WeatherEntity):
"""Return the weather condition."""
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]:
"""Return the daily forecast."""
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]
return ha_forecast
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
return self._forecast(False)
@callback
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""

View File

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

View File

@ -216,11 +216,6 @@ class MeteoFranceWeather(
)
return forecast_data
@property
def forecast(self) -> list[Forecast]:
"""Return the forecast array."""
return self._forecast(self._mode)
async def async_forecast_daily(self) -> list[Forecast]:
"""Return the daily forecast in native units."""
return self._forecast(FORECAST_MODE_DAILY)

View File

@ -181,14 +181,6 @@ class MetOfficeWeather(
return str(value) if value is not None else 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
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the twice daily forecast in native units."""

View File

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

View File

@ -88,9 +88,9 @@ class OpenMeteoWeatherEntity(
return None
return self.coordinator.data.current_weather.wind_direction
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast in native units."""
@callback
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""
if self.coordinator.data.daily is None:
return None
@ -124,8 +124,3 @@ class OpenMeteoWeatherEntity(
forecasts.append(forecast)
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]
@property
def forecast(self) -> list[Forecast] | None:
def _forecast(self) -> list[Forecast] | None:
"""Return the forecast array."""
api_forecasts = self.coordinator.data[ATTR_API_FORECAST]
forecasts = [
@ -201,9 +201,9 @@ class OpenWeatherMapWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordina
@callback
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""
return self.forecast
return self._forecast
@callback
def _async_forecast_hourly(self) -> list[Forecast] | None:
"""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."""
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(
self, forecast_data: list[SmhiForecast] | 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_OZONE_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_HOURLY_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._ozone_template = config.get(CONF_OZONE_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_hourly_template = config.get(CONF_FORECAST_HOURLY_TEMPLATE)
self._forecast_twice_daily_template = config.get(
@ -227,7 +225,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
self._cloud_coverage = None
self._dew_point = None
self._apparent_temperature = None
self._forecast: list[Forecast] = []
self._forecast_daily: list[Forecast] = []
self._forecast_hourly: list[Forecast] = []
self._forecast_twice_daily: list[Forecast] = []
@ -300,11 +297,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
"""Return the 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]:
"""Return the daily forecast in native units."""
return self._forecast_daily
@ -394,11 +386,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity):
"_apparent_temperature",
self._apparent_temperature_template,
)
if self._forecast_template:
self.add_template_attribute(
"_forecast",
self._forecast_template,
)
if self._forecast_daily_template:
self.add_template_attribute(

View File

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

View File

@ -3,7 +3,6 @@
from __future__ import annotations
import abc
import asyncio
from collections.abc import Callable, Iterable
from contextlib import suppress
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_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
import homeassistant.helpers.issue_registry as ir
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import (
@ -56,7 +54,6 @@ from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
TimestampDataUpdateCoordinator,
)
from homeassistant.loader import async_get_issue_tracker, async_suggest_report_issue
from homeassistant.util.dt import utcnow
from homeassistant.util.json import JsonValueType
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_WINDY = "windy"
ATTR_CONDITION_WINDY_VARIANT = "windy-variant"
ATTR_FORECAST = "forecast"
ATTR_FORECAST_IS_DAYTIME: Final = "is_daytime"
ATTR_FORECAST_CONDITION: Final = "condition"
ATTR_FORECAST_HUMIDITY: Final = "humidity"
@ -306,13 +302,8 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""ABC for weather data."""
_entity_component_unrecorded_attributes = frozenset({ATTR_FORECAST})
entity_description: WeatherEntityDescription
_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_ozone: float | 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"],
list[Callable[[list[JsonValueType] | None], None]],
]
__weather_reported_legacy_forecast = False
__weather_legacy_forecast = False
_weather_option_temperature_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."""
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:
"""Call when the weather entity is 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
@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:
"""Return the daily forecast in native units."""
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_PRECIPITATION_UNIT] = self._precipitation_unit
if self.forecast:
data[ATTR_FORECAST] = self._convert_forecast(self.forecast)
return data
@final

View File

@ -110,14 +110,6 @@
}
},
"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": {
"title": "Detected use of deprecated service `weather.get_forecast`",
"fix_flow": {

View File

@ -9,18 +9,7 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.accuweather.const import ATTRIBUTION
from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_FORECAST_APPARENT_TEMP,
ATTR_FORECAST_CLOUD_COVERAGE,
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_CLOUD_COVERAGE,
ATTR_WEATHER_DEW_POINT,
@ -35,7 +24,6 @@ from homeassistant.components.weather import (
DOMAIN as WEATHER_DOMAIN,
LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
WeatherEntityFeature,
)
from homeassistant.const import (
ATTR_ATTRIBUTION,
@ -58,16 +46,13 @@ from tests.common import (
from tests.typing import WebSocketGenerator
async def test_weather_without_forecast(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
async def test_weather(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> None:
"""Test states of the weather without forecast."""
await init_integration(hass)
state = hass.states.get("weather.home")
assert state
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_PRESSURE) == 1012.0
assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6
@ -87,49 +72,6 @@ async def test_weather_without_forecast(
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:
"""Ensure that we mark the entities unavailable correctly when service is offline."""
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.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_PRESSURE,
ATTR_WEATHER_TEMPERATURE,
@ -84,53 +76,6 @@ async def test_setup_config_flow(hass: HomeAssistant) -> None:
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:
"""Test for successfully setting up the IPMA platform."""
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("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))
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("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
weather = hass.states.get("weather.met_office_king_s_lynn_daily")
assert weather
@ -254,19 +228,6 @@ async def test_two_weather_sites_running(
assert weather.attributes.get("wind_bearing") == "ESE"
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))
async def test_new_config_entry(

View File

@ -12,7 +12,6 @@ from homeassistant.components import nws
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_SUNNY,
ATTR_FORECAST,
DOMAIN as WEATHER_DOMAIN,
LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
@ -77,10 +76,6 @@ async def test_imperial_metric(
for key, value in result_observation.items():
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:
"""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:
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:
"""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:
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:
"""Test error in setting station."""

View File

@ -1,338 +1,4 @@
# 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]
dict({
'forecast': list([
@ -671,138 +337,6 @@
ReadOnlyDict({
'attribution': 'Swedish weather institute (SMHI)',
'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',
'humidity': 100,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
@ -820,18 +354,3 @@
'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.weather import CONDITION_CLASSES, RETRY_TIMEOUT
from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_FORECAST_CONDITION,
ATTR_WEATHER_HUMIDITY,
ATTR_WEATHER_PRESSURE,
@ -65,10 +64,6 @@ async def test_setup_hass(
assert state
assert state.state == "fog"
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:
@ -95,7 +90,6 @@ async def test_properties_no_data(hass: HomeAssistant) -> None:
assert ATTR_WEATHER_VISIBILITY not in state.attributes
assert ATTR_WEATHER_WIND_SPEED 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_SMHI_THUNDER_PROBABILITY 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.name == "test"
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(
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]
dict({
'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
dict({
'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
dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 16.9,
}),
]),
})
# ---
@ -101,11 +86,6 @@
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]),
}),
})
@ -114,11 +94,6 @@
dict({
'weather.forecast': dict({
'forecast': list([
dict({
'condition': 'cloudy',
'datetime': '2023-02-17T14:00:00+00:00',
'temperature': 14.2,
}),
]),
}),
})
@ -141,11 +116,6 @@
dict({
'weather.forecast': dict({
'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 homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_WEATHER_APPARENT_TEMPERATURE,
ATTR_WEATHER_CLOUD_COVERAGE,
ATTR_WEATHER_DEW_POINT,
@ -36,6 +35,8 @@ from tests.common import (
mock_restore_cache_with_extra_data,
)
ATTR_FORECAST = "forecast"
@pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)])
@pytest.mark.parametrize(
@ -49,7 +50,6 @@ from tests.common import (
"name": "test",
"attribution_template": "{{ states('sensor.attribution') }}",
"condition_template": "sunny",
"forecast_template": "{{ states.weather.demo.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}",
"humidity_template": "{{ states('sensor.humidity') | int }}",
"pressure_template": "{{ states('sensor.pressure') }}",
@ -111,7 +111,6 @@ async def test_template_state_text(hass: HomeAssistant, start_ha) -> None:
"platform": "template",
"name": "forecast",
"condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_daily_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 }}",
@ -238,7 +237,6 @@ async def test_forecasts(
"platform": "template",
"name": "forecast",
"condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_daily_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_hourly_template": "{{ states.weather.forecast_hourly.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}",
@ -323,7 +321,6 @@ async def test_forecast_invalid(
"platform": "template",
"name": "forecast",
"condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}",
"humidity_template": "{{ states('sensor.humidity') | int }}",
@ -393,7 +390,6 @@ async def test_forecast_invalid_is_daytime_missing_in_twice_daily(
"platform": "template",
"name": "forecast",
"condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}",
"humidity_template": "{{ states('sensor.humidity') | int }}",
@ -463,7 +459,6 @@ async def test_forecast_invalid_datetime_missing(
"platform": "template",
"name": "forecast",
"condition_template": "sunny",
"forecast_template": "{{ states.weather.forecast.attributes.forecast }}",
"forecast_daily_template": "{{ states.weather.forecast_daily.attributes.forecast }}",
"forecast_hourly_template": "{{ states.weather.forecast_hourly.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}",
@ -728,7 +723,6 @@ async def test_trigger_action(
"cloud_coverage_template": "{{ my_variable + 1 }}",
"dew_point_template": "{{ my_variable + 1 }}",
"apparent_temperature_template": "{{ my_variable + 1 }}",
"forecast_template": "{{ var_forecast_daily }}",
"forecast_daily_template": "{{ var_forecast_daily }}",
"forecast_hourly_template": "{{ var_forecast_hourly }}",
"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 (
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_OZONE,
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.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_WEATHER_HUMIDITY] == 23
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)
assert weather_state.state == ATTR_CONDITION_SUNNY
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_WEATHER_HUMIDITY] == 23
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 (
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_OZONE,
ATTR_WEATHER_PRECIPITATION_UNIT,
@ -79,6 +68,7 @@ class MockWeatherEntity(WeatherEntity):
def __init__(self) -> None:
"""Initiate Entity."""
super().__init__()
self._attr_precision = PRECISION_TENTHS
self._attr_condition = ATTR_CONDITION_SUNNY
self._attr_native_precipitation_unit = UnitOfLength.MILLIMETERS
self._attr_native_pressure = 10
@ -92,14 +82,6 @@ class MockWeatherEntity(WeatherEntity):
self._attr_native_wind_gust_speed = 10
self._attr_native_wind_speed = 3
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 = [
Forecast(
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
)
class MockWeatherMock(MockWeatherTest):
"""Mock weather class."""
@property
def forecast(self) -> list[Forecast] | None:
"""Return the forecast."""
return self.forecast_list
kwargs = {
"native_temperature": native_value,
"native_temperature_unit": native_unit,
@ -156,10 +130,9 @@ async def test_temperature(
"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)
forecast_daily = state.attributes[ATTR_FORECAST][0]
expected = state_value
apparent_expected = apparent_state_value
@ -174,20 +147,6 @@ async def test_temperature(
dew_point_expected, rel=0.1
)
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])
@ -214,14 +173,6 @@ async def test_temperature_no_unit(
dew_point_state_value = dew_point_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 = {
"native_temperature": native_value,
"native_temperature_unit": native_unit,
@ -229,10 +180,9 @@ async def test_temperature_no_unit(
"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)
forecast = state.attributes[ATTR_FORECAST][0]
expected = 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
)
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(
@ -276,26 +218,16 @@ async def test_pressure(
native_value = 30
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}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value
assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == pytest.approx(
expected, rel=1e-2
)
assert float(forecast[ATTR_FORECAST_PRESSURE]) == pytest.approx(expected, rel=1e-2)
@pytest.mark.parametrize("native_unit", [None])
@ -315,26 +247,16 @@ async def test_pressure_no_unit(
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_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)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value
assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == pytest.approx(
expected, rel=1e-2
)
assert float(forecast[ATTR_FORECAST_PRESSURE]) == pytest.approx(expected, rel=1e-2)
@pytest.mark.parametrize(
@ -364,28 +286,16 @@ async def test_wind_speed(
native_value = 10
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}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx(
expected, rel=1e-2
)
assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == pytest.approx(
expected, rel=1e-2
)
@pytest.mark.parametrize(
@ -415,31 +325,19 @@ async def test_wind_gust_speed(
native_value = 10
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_gust_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)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value
assert float(state.attributes[ATTR_WEATHER_WIND_GUST_SPEED]) == pytest.approx(
expected, rel=1e-2
)
assert float(forecast[ATTR_FORECAST_WIND_GUST_SPEED]) == pytest.approx(
expected, rel=1e-2
)
@pytest.mark.parametrize("native_unit", [None])
@ -462,194 +360,16 @@ async def test_wind_speed_no_unit(
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_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)
forecast = state.attributes[ATTR_FORECAST][0]
expected = state_value
assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx(
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(
@ -662,14 +382,6 @@ async def test_wind_bearing_ozone_and_cloud_coverage_and_uv_index(
cloud_coverage = 75
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 = {
"wind_bearing": wind_bearing_value,
"ozone": ozone_value,
@ -677,15 +389,13 @@ async def test_wind_bearing_ozone_and_cloud_coverage_and_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)
forecast = state.attributes[ATTR_FORECAST][0]
assert float(state.attributes[ATTR_WEATHER_WIND_BEARING]) == 180
assert float(state.attributes[ATTR_WEATHER_OZONE]) == 10
assert float(state.attributes[ATTR_WEATHER_CLOUD_COVERAGE]) == 75
assert float(state.attributes[ATTR_WEATHER_UV_INDEX]) == 1.2
assert float(forecast[ATTR_FORECAST_UV_INDEX]) == 1.2
async def test_humidity(
@ -695,55 +405,12 @@ async def test_humidity(
"""Test humidity."""
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}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
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:
@ -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)
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 = {
"native_temperature": temperature_value,
"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",
}
entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs)
entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs)
state = hass.states.get(entity0.entity_id)
forecast = state.attributes[ATTR_FORECAST][0]
expected_wind_speed = round(
SpeedConverter.convert(
@ -820,12 +478,6 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N
),
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(
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(
expected_visibility
)
assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == pytest.approx(
expected_precipitation, rel=1e-2
)
assert (
state.attributes[ATTR_WEATHER_PRECIPITATION_UNIT]
@ -925,14 +574,6 @@ async def test_forecast_twice_daily_missing_is_daytime(
) -> None:
"""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 = {
"native_temperature": 38,
"native_temperature_unit": UnitOfTemperature.CELSIUS,
@ -940,7 +581,7 @@ async def test_forecast_twice_daily_missing_is_daytime(
"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)
@ -1147,155 +788,6 @@ async def test_get_forecast_unsupported(
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(
hass: HomeAssistant,
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:
"""Return the forecast_daily."""
return self.forecast_list