mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
Modernize accuweather weather (#99001)
This commit is contained in:
parent
f96c1516f8
commit
27f7399071
@ -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
|
||||||
|
225
tests/components/accuweather/snapshots/test_weather.ambr
Normal file
225
tests/components/accuweather/snapshots/test_weather.ambr
Normal 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,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
# ---
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user