Modernize accuweather weather (#99001)

This commit is contained in:
Erik Montnemery 2023-08-25 16:46:23 +02:00 committed by GitHub
parent f96c1516f8
commit 27f7399071
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 331 additions and 5 deletions

View File

@ -17,7 +17,8 @@ from homeassistant.components.weather import (
ATTR_FORECAST_UV_INDEX, ATTR_FORECAST_UV_INDEX,
ATTR_FORECAST_WIND_BEARING, ATTR_FORECAST_WIND_BEARING,
Forecast, Forecast,
WeatherEntity, SingleCoordinatorWeatherEntity,
WeatherEntityFeature,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@ -27,9 +28,8 @@ from homeassistant.const import (
UnitOfSpeed, UnitOfSpeed,
UnitOfTemperature, UnitOfTemperature,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import utc_from_timestamp from homeassistant.util.dt import utc_from_timestamp
from . import AccuWeatherDataUpdateCoordinator from . import AccuWeatherDataUpdateCoordinator
@ -58,7 +58,7 @@ async def async_setup_entry(
class AccuWeatherEntity( class AccuWeatherEntity(
CoordinatorEntity[AccuWeatherDataUpdateCoordinator], WeatherEntity SingleCoordinatorWeatherEntity[AccuWeatherDataUpdateCoordinator]
): ):
"""Define an AccuWeather entity.""" """Define an AccuWeather entity."""
@ -76,6 +76,8 @@ class AccuWeatherEntity(
self._attr_unique_id = coordinator.location_key self._attr_unique_id = coordinator.location_key
self._attr_attribution = ATTRIBUTION self._attr_attribution = ATTRIBUTION
self._attr_device_info = coordinator.device_info self._attr_device_info = coordinator.device_info
if self.coordinator.forecast:
self._attr_supported_features = WeatherEntityFeature.FORECAST_DAILY
@property @property
def condition(self) -> str | None: def condition(self) -> str | None:
@ -174,3 +176,8 @@ 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

@ -0,0 +1,225 @@
# serializer version: 1
# name: test_forecast_service
dict({
'forecast': list([
dict({
'apparent_temperature': 29.8,
'cloud_coverage': 58,
'condition': 'lightning-rainy',
'datetime': '2020-07-26T05:00:00+00:00',
'precipitation': 2.5,
'precipitation_probability': 60,
'temperature': 29.5,
'templow': 15.4,
'uv_index': 5,
'wind_bearing': 166,
'wind_gust_speed': 29.6,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 28.9,
'cloud_coverage': 52,
'condition': 'partlycloudy',
'datetime': '2020-07-27T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 25,
'temperature': 26.2,
'templow': 15.9,
'uv_index': 7,
'wind_bearing': 297,
'wind_gust_speed': 14.8,
'wind_speed': 9.3,
}),
dict({
'apparent_temperature': 31.6,
'cloud_coverage': 65,
'condition': 'partlycloudy',
'datetime': '2020-07-28T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 10,
'temperature': 31.7,
'templow': 16.8,
'uv_index': 7,
'wind_bearing': 198,
'wind_gust_speed': 24.1,
'wind_speed': 16.7,
}),
dict({
'apparent_temperature': 26.5,
'cloud_coverage': 45,
'condition': 'partlycloudy',
'datetime': '2020-07-29T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 9,
'temperature': 24.0,
'templow': 11.7,
'uv_index': 6,
'wind_bearing': 293,
'wind_gust_speed': 24.1,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 22.2,
'cloud_coverage': 50,
'condition': 'partlycloudy',
'datetime': '2020-07-30T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 1,
'temperature': 21.4,
'templow': 12.2,
'uv_index': 7,
'wind_bearing': 280,
'wind_gust_speed': 27.8,
'wind_speed': 18.5,
}),
]),
})
# ---
# name: test_forecast_subscription
list([
dict({
'apparent_temperature': 29.8,
'cloud_coverage': 58,
'condition': 'lightning-rainy',
'datetime': '2020-07-26T05:00:00+00:00',
'precipitation': 2.5,
'precipitation_probability': 60,
'temperature': 29.5,
'templow': 15.4,
'uv_index': 5,
'wind_bearing': 166,
'wind_gust_speed': 29.6,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 28.9,
'cloud_coverage': 52,
'condition': 'partlycloudy',
'datetime': '2020-07-27T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 25,
'temperature': 26.2,
'templow': 15.9,
'uv_index': 7,
'wind_bearing': 297,
'wind_gust_speed': 14.8,
'wind_speed': 9.3,
}),
dict({
'apparent_temperature': 31.6,
'cloud_coverage': 65,
'condition': 'partlycloudy',
'datetime': '2020-07-28T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 10,
'temperature': 31.7,
'templow': 16.8,
'uv_index': 7,
'wind_bearing': 198,
'wind_gust_speed': 24.1,
'wind_speed': 16.7,
}),
dict({
'apparent_temperature': 26.5,
'cloud_coverage': 45,
'condition': 'partlycloudy',
'datetime': '2020-07-29T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 9,
'temperature': 24.0,
'templow': 11.7,
'uv_index': 6,
'wind_bearing': 293,
'wind_gust_speed': 24.1,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 22.2,
'cloud_coverage': 50,
'condition': 'partlycloudy',
'datetime': '2020-07-30T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 1,
'temperature': 21.4,
'templow': 12.2,
'uv_index': 7,
'wind_bearing': 280,
'wind_gust_speed': 27.8,
'wind_speed': 18.5,
}),
])
# ---
# name: test_forecast_subscription.1
list([
dict({
'apparent_temperature': 29.8,
'cloud_coverage': 58,
'condition': 'lightning-rainy',
'datetime': '2020-07-26T05:00:00+00:00',
'precipitation': 2.5,
'precipitation_probability': 60,
'temperature': 29.5,
'templow': 15.4,
'uv_index': 5,
'wind_bearing': 166,
'wind_gust_speed': 29.6,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 28.9,
'cloud_coverage': 52,
'condition': 'partlycloudy',
'datetime': '2020-07-27T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 25,
'temperature': 26.2,
'templow': 15.9,
'uv_index': 7,
'wind_bearing': 297,
'wind_gust_speed': 14.8,
'wind_speed': 9.3,
}),
dict({
'apparent_temperature': 31.6,
'cloud_coverage': 65,
'condition': 'partlycloudy',
'datetime': '2020-07-28T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 10,
'temperature': 31.7,
'templow': 16.8,
'uv_index': 7,
'wind_bearing': 198,
'wind_gust_speed': 24.1,
'wind_speed': 16.7,
}),
dict({
'apparent_temperature': 26.5,
'cloud_coverage': 45,
'condition': 'partlycloudy',
'datetime': '2020-07-29T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 9,
'temperature': 24.0,
'templow': 11.7,
'uv_index': 6,
'wind_bearing': 293,
'wind_gust_speed': 24.1,
'wind_speed': 13.0,
}),
dict({
'apparent_temperature': 22.2,
'cloud_coverage': 50,
'condition': 'partlycloudy',
'datetime': '2020-07-30T05:00:00+00:00',
'precipitation': 0.0,
'precipitation_probability': 1,
'temperature': 21.4,
'templow': 12.2,
'uv_index': 7,
'wind_bearing': 280,
'wind_gust_speed': 27.8,
'wind_speed': 18.5,
}),
])
# ---

View File

@ -2,6 +2,9 @@
from datetime import timedelta from datetime import timedelta
from unittest.mock import PropertyMock, patch from unittest.mock import PropertyMock, patch
from freezegun.api import FrozenDateTimeFactory
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,
@ -27,8 +30,16 @@ from homeassistant.components.weather import (
ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_BEARING,
ATTR_WEATHER_WIND_GUST_SPEED, ATTR_WEATHER_WIND_GUST_SPEED,
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECAST,
WeatherEntityFeature,
)
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
STATE_UNAVAILABLE,
) )
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -41,6 +52,7 @@ from tests.common import (
load_json_array_fixture, load_json_array_fixture,
load_json_object_fixture, load_json_object_fixture,
) )
from tests.typing import WebSocketGenerator
async def test_weather_without_forecast(hass: HomeAssistant) -> None: async def test_weather_without_forecast(hass: HomeAssistant) -> None:
@ -64,6 +76,7 @@ async def test_weather_without_forecast(hass: HomeAssistant) -> None:
assert state.attributes.get(ATTR_WEATHER_WIND_GUST_SPEED) == 20.3 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_WEATHER_UV_INDEX) == 6
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert ATTR_SUPPORTED_FEATURES not in state.attributes
entry = registry.async_get("weather.home") entry = registry.async_get("weather.home")
assert entry assert entry
@ -90,6 +103,9 @@ async def test_weather_with_forecast(hass: HomeAssistant) -> None:
assert state.attributes.get(ATTR_WEATHER_WIND_GUST_SPEED) == 20.3 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_WEATHER_UV_INDEX) == 6
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes[ATTR_SUPPORTED_FEATURES] == WeatherEntityFeature.FORECAST_DAILY
)
forecast = state.attributes.get(ATTR_FORECAST)[0] forecast = state.attributes.get(ATTR_FORECAST)[0]
assert forecast.get(ATTR_FORECAST_CONDITION) == "lightning-rainy" assert forecast.get(ATTR_FORECAST_CONDITION) == "lightning-rainy"
assert forecast.get(ATTR_FORECAST_PRECIPITATION) == 2.5 assert forecast.get(ATTR_FORECAST_PRECIPITATION) == 2.5
@ -186,3 +202,81 @@ async def test_unsupported_condition_icon_data(hass: HomeAssistant) -> None:
state = hass.states.get("weather.home") state = hass.states.get("weather.home")
assert state.attributes.get(ATTR_FORECAST_CONDITION) is None assert state.attributes.get(ATTR_FORECAST_CONDITION) is None
async def test_forecast_service(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
) -> None:
"""Test multiple forecast."""
await init_integration(hass, forecast=True)
response = await hass.services.async_call(
WEATHER_DOMAIN,
SERVICE_GET_FORECAST,
{
"entity_id": "weather.home",
"type": "daily",
},
blocking=True,
return_response=True,
)
assert response["forecast"] != []
assert response == snapshot
async def test_forecast_subscription(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None:
"""Test multiple forecast."""
client = await hass_ws_client(hass)
await init_integration(hass, forecast=True)
await client.send_json_auto_id(
{
"type": "weather/subscribe_forecast",
"forecast_type": "daily",
"entity_id": "weather.home",
}
)
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] is None
subscription_id = msg["id"]
msg = await client.receive_json()
assert msg["id"] == subscription_id
assert msg["type"] == "event"
forecast1 = msg["event"]["forecast"]
assert forecast1 != []
assert forecast1 == snapshot
current = load_json_object_fixture("accuweather/current_conditions_data.json")
forecast = load_json_array_fixture("accuweather/forecast_data.json")
with patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
return_value=current,
), patch(
"homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast",
return_value=forecast,
), patch(
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
new_callable=PropertyMock,
return_value=10,
):
freezer.tick(timedelta(minutes=80) + timedelta(seconds=1))
await hass.async_block_till_done()
msg = await client.receive_json()
assert msg["id"] == subscription_id
assert msg["type"] == "event"
forecast2 = msg["event"]["forecast"]
assert forecast2 != []
assert forecast2 == snapshot