Use separate data coordinators for AccuWeather observation and forecast (#115628)

* Remove forecast option

* Update strings

* Use separate DataUpdateCoordinator for observation and forecast

* Fix tests

* Remove unneeded variable

* Separate data coordinator classes

* Use list comprehension

* Separate coordinator clasess to add type annotations

* Test the availability of the forecast sensor entity

* Add DataUpdateCoordinator types

* Use snapshot for test_sensor()

---------

Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
This commit is contained in:
Maciej Bieniek 2024-04-17 00:00:16 +02:00 committed by GitHub
parent 81036967f0
commit e7076ac83f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 6913 additions and 852 deletions

View file

@ -2,14 +2,10 @@
from __future__ import annotations
from asyncio import timeout
from datetime import timedelta
from dataclasses import dataclass
import logging
from typing import Any
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
from aiohttp import ClientSession
from aiohttp.client_exceptions import ClientConnectorError
from accuweather import AccuWeather
from homeassistant.components.sensor import DOMAIN as SENSOR_PLATFORM
from homeassistant.config_entries import ConfigEntry
@ -17,43 +13,70 @@ from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN, MANUFACTURER
from .const import DOMAIN, UPDATE_INTERVAL_DAILY_FORECAST, UPDATE_INTERVAL_OBSERVATION
from .coordinator import (
AccuWeatherDailyForecastDataUpdateCoordinator,
AccuWeatherObservationDataUpdateCoordinator,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
@dataclass
class AccuWeatherData:
"""Data for AccuWeather integration."""
coordinator_observation: AccuWeatherObservationDataUpdateCoordinator
coordinator_daily_forecast: AccuWeatherDailyForecastDataUpdateCoordinator
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AccuWeather as config entry."""
api_key: str = entry.data[CONF_API_KEY]
name: str = entry.data[CONF_NAME]
assert entry.unique_id is not None
location_key = entry.unique_id
forecast: bool = entry.options.get(CONF_FORECAST, False)
_LOGGER.debug("Using location_key: %s, get forecast: %s", location_key, forecast)
location_key = entry.unique_id
_LOGGER.debug("Using location_key: %s", location_key)
websession = async_get_clientsession(hass)
accuweather = AccuWeather(api_key, websession, location_key=location_key)
coordinator = AccuWeatherDataUpdateCoordinator(
hass, websession, api_key, location_key, forecast, name
coordinator_observation = AccuWeatherObservationDataUpdateCoordinator(
hass,
accuweather,
name,
"observation",
UPDATE_INTERVAL_OBSERVATION,
)
await coordinator.async_config_entry_first_refresh()
coordinator_daily_forecast = AccuWeatherDailyForecastDataUpdateCoordinator(
hass,
accuweather,
name,
"daily forecast",
UPDATE_INTERVAL_DAILY_FORECAST,
)
await coordinator_observation.async_config_entry_first_refresh()
await coordinator_daily_forecast.async_config_entry_first_refresh()
entry.async_on_unload(entry.add_update_listener(update_listener))
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AccuWeatherData(
coordinator_observation=coordinator_observation,
coordinator_daily_forecast=coordinator_daily_forecast,
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# Remove ozone sensors from registry if they exist
ent_reg = er.async_get(hass)
for day in range(5):
unique_id = f"{coordinator.location_key}-ozone-{day}"
unique_id = f"{location_key}-ozone-{day}"
if entity_id := ent_reg.async_get_entity_id(SENSOR_PLATFORM, DOMAIN, unique_id):
_LOGGER.debug("Removing ozone sensor entity %s", entity_id)
ent_reg.async_remove(entity_id)
@ -74,65 +97,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener."""
await hass.config_entries.async_reload(entry.entry_id)
class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): # pylint: disable=hass-enforce-coordinator-module
"""Class to manage fetching AccuWeather data API."""
def __init__(
self,
hass: HomeAssistant,
session: ClientSession,
api_key: str,
location_key: str,
forecast: bool,
name: str,
) -> None:
"""Initialize."""
self.location_key = location_key
self.forecast = forecast
self.accuweather = AccuWeather(api_key, session, location_key=location_key)
self.device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, location_key)},
manufacturer=MANUFACTURER,
name=name,
# You don't need to provide specific details for the URL,
# so passing in _ characters is fine if the location key
# is correct
configuration_url=(
"http://accuweather.com/en/"
f"_/_/{location_key}/"
f"weather-forecast/{location_key}/"
),
)
# Enabling the forecast download increases the number of requests per data
# update, we use 40 minutes for current condition only and 80 minutes for
# current condition and forecast as update interval to not exceed allowed number
# of requests. We have 50 requests allowed per day, so we use 36 and leave 14 as
# a reserve for restarting HA.
update_interval = timedelta(minutes=40)
if self.forecast:
update_interval *= 2
_LOGGER.debug("Data will be update every %s", update_interval)
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
async def _async_update_data(self) -> dict[str, Any]:
"""Update data via library."""
forecast: list[dict[str, Any]] = []
try:
async with timeout(10):
current = await self.accuweather.async_get_current_conditions()
if self.forecast:
forecast = await self.accuweather.async_get_daily_forecast()
except (
ApiError,
ClientConnectorError,
InvalidApiKeyError,
RequestsExceededError,
) as error:
raise UpdateFailed(error) from error
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
return {**current, ATTR_FORECAST: forecast}

View file

@ -10,26 +10,12 @@ from aiohttp import ClientError
from aiohttp.client_exceptions import ClientConnectorError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.schema_config_entry_flow import (
SchemaFlowFormStep,
SchemaOptionsFlowHandler,
)
from .const import CONF_FORECAST, DOMAIN
OPTIONS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_FORECAST, default=False): bool,
}
)
OPTIONS_FLOW = {
"init": SchemaFlowFormStep(OPTIONS_SCHEMA),
}
from .const import DOMAIN
class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
@ -87,9 +73,3 @@ class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
),
errors=errors,
)
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> SchemaOptionsFlowHandler:
"""Options callback for AccuWeather."""
return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW)

View file

@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import timedelta
from typing import Final
from homeassistant.components.weather import (
@ -27,10 +28,8 @@ ATTR_CATEGORY: Final = "Category"
ATTR_DIRECTION: Final = "Direction"
ATTR_ENGLISH: Final = "English"
ATTR_LEVEL: Final = "level"
ATTR_FORECAST: Final = "forecast"
ATTR_SPEED: Final = "Speed"
ATTR_VALUE: Final = "Value"
CONF_FORECAST: Final = "forecast"
DOMAIN: Final = "accuweather"
MANUFACTURER: Final = "AccuWeather, Inc."
MAX_FORECAST_DAYS: Final = 4
@ -56,3 +55,5 @@ CONDITION_MAP = {
for cond_ha, cond_codes in CONDITION_CLASSES.items()
for cond_code in cond_codes
}
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=40)
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)

View file

@ -0,0 +1,124 @@
"""The AccuWeather coordinator."""
from asyncio import timeout
from datetime import timedelta
import logging
from typing import TYPE_CHECKING, Any
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
from aiohttp.client_exceptions import ClientConnectorError
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
TimestampDataUpdateCoordinator,
UpdateFailed,
)
from .const import DOMAIN, MANUFACTURER
EXCEPTIONS = (ApiError, ClientConnectorError, InvalidApiKeyError, RequestsExceededError)
_LOGGER = logging.getLogger(__name__)
class AccuWeatherObservationDataUpdateCoordinator(
DataUpdateCoordinator[dict[str, Any]]
):
"""Class to manage fetching AccuWeather data API."""
def __init__(
self,
hass: HomeAssistant,
accuweather: AccuWeather,
name: str,
coordinator_type: str,
update_interval: timedelta,
) -> None:
"""Initialize."""
self.accuweather = accuweather
self.location_key = accuweather.location_key
if TYPE_CHECKING:
assert self.location_key is not None
self.device_info = _get_device_info(self.location_key, name)
super().__init__(
hass,
_LOGGER,
name=f"{name} ({coordinator_type})",
update_interval=update_interval,
)
async def _async_update_data(self) -> dict[str, Any]:
"""Update data via library."""
try:
async with timeout(10):
result = await self.accuweather.async_get_current_conditions()
except EXCEPTIONS as error:
raise UpdateFailed(error) from error
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
return result
class AccuWeatherDailyForecastDataUpdateCoordinator(
TimestampDataUpdateCoordinator[list[dict[str, Any]]]
):
"""Class to manage fetching AccuWeather data API."""
def __init__(
self,
hass: HomeAssistant,
accuweather: AccuWeather,
name: str,
coordinator_type: str,
update_interval: timedelta,
) -> None:
"""Initialize."""
self.accuweather = accuweather
self.location_key = accuweather.location_key
if TYPE_CHECKING:
assert self.location_key is not None
self.device_info = _get_device_info(self.location_key, name)
super().__init__(
hass,
_LOGGER,
name=f"{name} ({coordinator_type})",
update_interval=update_interval,
)
async def _async_update_data(self) -> list[dict[str, Any]]:
"""Update data via library."""
try:
async with timeout(10):
result = await self.accuweather.async_get_daily_forecast()
except EXCEPTIONS as error:
raise UpdateFailed(error) from error
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
return result
def _get_device_info(location_key: str, name: str) -> DeviceInfo:
"""Get device info."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, location_key)},
manufacturer=MANUFACTURER,
name=name,
# You don't need to provide specific details for the URL,
# so passing in _ characters is fine if the location key
# is correct
configuration_url=(
"http://accuweather.com/en/"
f"_/_/{location_key}/weather-forecast/{location_key}/"
),
)

View file

@ -9,7 +9,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import HomeAssistant
from . import AccuWeatherDataUpdateCoordinator
from . import AccuWeatherData
from .const import DOMAIN
TO_REDACT = {CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE}
@ -19,11 +19,9 @@ async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
accuweather_data: AccuWeatherData = hass.data[DOMAIN][config_entry.entry_id]
return {
"config_entry_data": async_redact_data(dict(config_entry.data), TO_REDACT),
"coordinator_data": coordinator.data,
"observation_data": accuweather_data.coordinator_observation.data,
}

View file

@ -28,13 +28,12 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AccuWeatherDataUpdateCoordinator
from . import AccuWeatherData
from .const import (
API_METRIC,
ATTR_CATEGORY,
ATTR_DIRECTION,
ATTR_ENGLISH,
ATTR_FORECAST,
ATTR_LEVEL,
ATTR_SPEED,
ATTR_VALUE,
@ -42,6 +41,10 @@ from .const import (
DOMAIN,
MAX_FORECAST_DAYS,
)
from .coordinator import (
AccuWeatherDailyForecastDataUpdateCoordinator,
AccuWeatherObservationDataUpdateCoordinator,
)
PARALLEL_UPDATES = 1
@ -52,12 +55,18 @@ class AccuWeatherSensorDescription(SensorEntityDescription):
value_fn: Callable[[dict[str, Any]], str | int | float | None]
attr_fn: Callable[[dict[str, Any]], dict[str, Any]] = lambda _: {}
day: int | None = None
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
@dataclass(frozen=True, kw_only=True)
class AccuWeatherForecastSensorDescription(AccuWeatherSensorDescription):
"""Class describing AccuWeather sensor entities."""
day: int
FORECAST_SENSOR_TYPES: tuple[AccuWeatherForecastSensorDescription, ...] = (
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="AirQuality",
icon="mdi:air-filter",
value_fn=lambda data: cast(str, data[ATTR_CATEGORY]),
@ -69,7 +78,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="CloudCoverDay",
icon="mdi:weather-cloudy",
entity_registry_enabled_default=False,
@ -81,7 +90,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="CloudCoverNight",
icon="mdi:weather-cloudy",
entity_registry_enabled_default=False,
@ -93,7 +102,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="Grass",
icon="mdi:grass",
entity_registry_enabled_default=False,
@ -106,7 +115,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="HoursOfSun",
icon="mdi:weather-partly-cloudy",
native_unit_of_measurement=UnitOfTime.HOURS,
@ -117,7 +126,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="LongPhraseDay",
value_fn=lambda data: cast(str, data),
translation_key=f"condition_day_{day}d",
@ -126,7 +135,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="LongPhraseNight",
value_fn=lambda data: cast(str, data),
translation_key=f"condition_night_{day}d",
@ -135,7 +144,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="Mold",
icon="mdi:blur",
entity_registry_enabled_default=False,
@ -148,7 +157,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="Ragweed",
icon="mdi:sprout",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -161,7 +170,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="RealFeelTemperatureMax",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
@ -172,7 +181,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="RealFeelTemperatureMin",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
@ -183,7 +192,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="RealFeelTemperatureShadeMax",
device_class=SensorDeviceClass.TEMPERATURE,
entity_registry_enabled_default=False,
@ -195,7 +204,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="RealFeelTemperatureShadeMin",
device_class=SensorDeviceClass.TEMPERATURE,
entity_registry_enabled_default=False,
@ -207,7 +216,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="SolarIrradianceDay",
icon="mdi:weather-sunny",
entity_registry_enabled_default=False,
@ -219,7 +228,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="SolarIrradianceNight",
icon="mdi:weather-sunny",
entity_registry_enabled_default=False,
@ -231,7 +240,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="ThunderstormProbabilityDay",
icon="mdi:weather-lightning",
native_unit_of_measurement=PERCENTAGE,
@ -242,7 +251,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="ThunderstormProbabilityNight",
icon="mdi:weather-lightning",
native_unit_of_measurement=PERCENTAGE,
@ -253,7 +262,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="Tree",
icon="mdi:tree-outline",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
@ -266,7 +275,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="UVIndex",
icon="mdi:weather-sunny",
native_unit_of_measurement=UV_INDEX,
@ -278,7 +287,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="WindGustDay",
device_class=SensorDeviceClass.WIND_SPEED,
entity_registry_enabled_default=False,
@ -291,7 +300,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="WindGustNight",
device_class=SensorDeviceClass.WIND_SPEED,
entity_registry_enabled_default=False,
@ -304,7 +313,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="WindDay",
device_class=SensorDeviceClass.WIND_SPEED,
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
@ -316,7 +325,7 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
for day in range(MAX_FORECAST_DAYS + 1)
),
*(
AccuWeatherSensorDescription(
AccuWeatherForecastSensorDescription(
key="WindNight",
device_class=SensorDeviceClass.WIND_SPEED,
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
@ -453,25 +462,33 @@ async def async_setup_entry(
) -> None:
"""Add AccuWeather entities from a config_entry."""
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
accuweather_data: AccuWeatherData = hass.data[DOMAIN][entry.entry_id]
sensors = [
AccuWeatherSensor(coordinator, description) for description in SENSOR_TYPES
observation_coordinator: AccuWeatherObservationDataUpdateCoordinator = (
accuweather_data.coordinator_observation
)
forecast_daily_coordinator: AccuWeatherDailyForecastDataUpdateCoordinator = (
accuweather_data.coordinator_daily_forecast
)
sensors: list[AccuWeatherSensor | AccuWeatherForecastSensor] = [
AccuWeatherSensor(observation_coordinator, description)
for description in SENSOR_TYPES
]
if coordinator.forecast:
for description in FORECAST_SENSOR_TYPES:
# Some air quality/allergy sensors are only available for certain
# locations.
if description.key not in coordinator.data[ATTR_FORECAST][description.day]:
continue
sensors.append(AccuWeatherSensor(coordinator, description))
sensors.extend(
[
AccuWeatherForecastSensor(forecast_daily_coordinator, description)
for description in FORECAST_SENSOR_TYPES
if description.key in forecast_daily_coordinator.data[description.day]
]
)
async_add_entities(sensors)
class AccuWeatherSensor(
CoordinatorEntity[AccuWeatherDataUpdateCoordinator], SensorEntity
CoordinatorEntity[AccuWeatherObservationDataUpdateCoordinator], SensorEntity
):
"""Define an AccuWeather entity."""
@ -481,22 +498,15 @@ class AccuWeatherSensor(
def __init__(
self,
coordinator: AccuWeatherDataUpdateCoordinator,
coordinator: AccuWeatherObservationDataUpdateCoordinator,
description: AccuWeatherSensorDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self.forecast_day = description.day
self.entity_description = description
self._sensor_data = _get_sensor_data(
coordinator.data, description.key, self.forecast_day
)
if self.forecast_day is not None:
self._attr_unique_id = f"{coordinator.location_key}-{description.key}-{self.forecast_day}".lower()
else:
self._attr_unique_id = (
f"{coordinator.location_key}-{description.key}".lower()
)
self._sensor_data = self._get_sensor_data(coordinator.data, description.key)
self._attr_unique_id = f"{coordinator.location_key}-{description.key}".lower()
self._attr_device_info = coordinator.device_info
@property
@ -507,30 +517,78 @@ class AccuWeatherSensor(
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
if self.forecast_day is not None:
return self.entity_description.attr_fn(self._sensor_data)
return self.entity_description.attr_fn(self.coordinator.data)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle data update."""
self._sensor_data = _get_sensor_data(
self._sensor_data = self._get_sensor_data(
self.coordinator.data, self.entity_description.key
)
self.async_write_ha_state()
@staticmethod
def _get_sensor_data(
sensors: dict[str, Any],
kind: str,
) -> Any:
"""Get sensor data."""
if kind == "Precipitation":
return sensors["PrecipitationSummary"]["PastHour"]
return sensors[kind]
class AccuWeatherForecastSensor(
CoordinatorEntity[AccuWeatherDailyForecastDataUpdateCoordinator], SensorEntity
):
"""Define an AccuWeather entity."""
_attr_attribution = ATTRIBUTION
_attr_has_entity_name = True
entity_description: AccuWeatherForecastSensorDescription
def __init__(
self,
coordinator: AccuWeatherDailyForecastDataUpdateCoordinator,
description: AccuWeatherForecastSensorDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self.forecast_day = description.day
self.entity_description = description
self._sensor_data = self._get_sensor_data(
coordinator.data, description.key, self.forecast_day
)
self._attr_unique_id = (
f"{coordinator.location_key}-{description.key}-{self.forecast_day}".lower()
)
self._attr_device_info = coordinator.device_info
@property
def native_value(self) -> str | int | float | None:
"""Return the state."""
return self.entity_description.value_fn(self._sensor_data)
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
return self.entity_description.attr_fn(self._sensor_data)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle data update."""
self._sensor_data = self._get_sensor_data(
self.coordinator.data, self.entity_description.key, self.forecast_day
)
self.async_write_ha_state()
def _get_sensor_data(
sensors: dict[str, Any],
kind: str,
forecast_day: int | None = None,
) -> Any:
"""Get sensor data."""
if forecast_day is not None:
return sensors[ATTR_FORECAST][forecast_day][kind]
if kind == "Precipitation":
return sensors["PrecipitationSummary"]["PastHour"]
return sensors[kind]
@staticmethod
def _get_sensor_data(
sensors: list[dict[str, Any]],
kind: str,
forecast_day: int,
) -> Any:
"""Get sensor data."""
return sensors[forecast_day][kind]

View file

@ -11,7 +11,7 @@
}
},
"create_entry": {
"default": "Some sensors are not enabled by default. You can enable them in the entity registry after the integration configuration.\nWeather forecast is not enabled by default. You can enable it in the integration options."
"default": "Some sensors are not enabled by default. You can enable them in the entity registry after the integration configuration."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@ -790,16 +790,6 @@
}
}
},
"options": {
"step": {
"init": {
"description": "Due to the limitations of the free version of the AccuWeather API key, when you enable weather forecast, data updates will be performed every 80 minutes instead of every 40 minutes.",
"data": {
"forecast": "Weather forecast"
}
}
}
},
"system_health": {
"info": {
"can_reach_server": "Reach AccuWeather server",

View file

@ -17,8 +17,8 @@ from homeassistant.components.weather import (
ATTR_FORECAST_TIME,
ATTR_FORECAST_UV_INDEX,
ATTR_FORECAST_WIND_BEARING,
CoordinatorWeatherEntity,
Forecast,
SingleCoordinatorWeatherEntity,
WeatherEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
@ -31,19 +31,23 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator
from homeassistant.util.dt import utc_from_timestamp
from . import AccuWeatherDataUpdateCoordinator
from . import AccuWeatherData
from .const import (
API_METRIC,
ATTR_DIRECTION,
ATTR_FORECAST,
ATTR_SPEED,
ATTR_VALUE,
ATTRIBUTION,
CONDITION_MAP,
DOMAIN,
)
from .coordinator import (
AccuWeatherDailyForecastDataUpdateCoordinator,
AccuWeatherObservationDataUpdateCoordinator,
)
PARALLEL_UPDATES = 1
@ -52,106 +56,134 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Add a AccuWeather weather entity from a config_entry."""
accuweather_data: AccuWeatherData = hass.data[DOMAIN][entry.entry_id]
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([AccuWeatherEntity(coordinator)])
async_add_entities([AccuWeatherEntity(accuweather_data)])
class AccuWeatherEntity(
SingleCoordinatorWeatherEntity[AccuWeatherDataUpdateCoordinator]
CoordinatorWeatherEntity[
AccuWeatherObservationDataUpdateCoordinator,
AccuWeatherDailyForecastDataUpdateCoordinator,
TimestampDataUpdateCoordinator,
TimestampDataUpdateCoordinator,
]
):
"""Define an AccuWeather entity."""
_attr_has_entity_name = True
_attr_name = None
def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
def __init__(self, accuweather_data: AccuWeatherData) -> None:
"""Initialize."""
super().__init__(coordinator)
super().__init__(
observation_coordinator=accuweather_data.coordinator_observation,
daily_coordinator=accuweather_data.coordinator_daily_forecast,
)
self._attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
self._attr_native_pressure_unit = UnitOfPressure.HPA
self._attr_native_temperature_unit = UnitOfTemperature.CELSIUS
self._attr_native_visibility_unit = UnitOfLength.KILOMETERS
self._attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
self._attr_unique_id = coordinator.location_key
self._attr_unique_id = accuweather_data.coordinator_observation.location_key
self._attr_attribution = ATTRIBUTION
self._attr_device_info = coordinator.device_info
if self.coordinator.forecast:
self._attr_supported_features = WeatherEntityFeature.FORECAST_DAILY
self._attr_device_info = accuweather_data.coordinator_observation.device_info
self._attr_supported_features = WeatherEntityFeature.FORECAST_DAILY
self.observation_coordinator = accuweather_data.coordinator_observation
self.daily_coordinator = accuweather_data.coordinator_daily_forecast
@property
def condition(self) -> str | None:
"""Return the current condition."""
return CONDITION_MAP.get(self.coordinator.data["WeatherIcon"])
return CONDITION_MAP.get(self.observation_coordinator.data["WeatherIcon"])
@property
def cloud_coverage(self) -> float:
"""Return the Cloud coverage in %."""
return cast(float, self.coordinator.data["CloudCover"])
return cast(float, self.observation_coordinator.data["CloudCover"])
@property
def native_apparent_temperature(self) -> float:
"""Return the apparent temperature."""
return cast(
float, self.coordinator.data["ApparentTemperature"][API_METRIC][ATTR_VALUE]
float,
self.observation_coordinator.data["ApparentTemperature"][API_METRIC][
ATTR_VALUE
],
)
@property
def native_temperature(self) -> float:
"""Return the temperature."""
return cast(float, self.coordinator.data["Temperature"][API_METRIC][ATTR_VALUE])
return cast(
float,
self.observation_coordinator.data["Temperature"][API_METRIC][ATTR_VALUE],
)
@property
def native_pressure(self) -> float:
"""Return the pressure."""
return cast(float, self.coordinator.data["Pressure"][API_METRIC][ATTR_VALUE])
return cast(
float, self.observation_coordinator.data["Pressure"][API_METRIC][ATTR_VALUE]
)
@property
def native_dew_point(self) -> float:
"""Return the dew point."""
return cast(float, self.coordinator.data["DewPoint"][API_METRIC][ATTR_VALUE])
return cast(
float, self.observation_coordinator.data["DewPoint"][API_METRIC][ATTR_VALUE]
)
@property
def humidity(self) -> int:
"""Return the humidity."""
return cast(int, self.coordinator.data["RelativeHumidity"])
return cast(int, self.observation_coordinator.data["RelativeHumidity"])
@property
def native_wind_gust_speed(self) -> float:
"""Return the wind gust speed."""
return cast(
float, self.coordinator.data["WindGust"][ATTR_SPEED][API_METRIC][ATTR_VALUE]
float,
self.observation_coordinator.data["WindGust"][ATTR_SPEED][API_METRIC][
ATTR_VALUE
],
)
@property
def native_wind_speed(self) -> float:
"""Return the wind speed."""
return cast(
float, self.coordinator.data["Wind"][ATTR_SPEED][API_METRIC][ATTR_VALUE]
float,
self.observation_coordinator.data["Wind"][ATTR_SPEED][API_METRIC][
ATTR_VALUE
],
)
@property
def wind_bearing(self) -> int:
"""Return the wind bearing."""
return cast(int, self.coordinator.data["Wind"][ATTR_DIRECTION]["Degrees"])
return cast(
int, self.observation_coordinator.data["Wind"][ATTR_DIRECTION]["Degrees"]
)
@property
def native_visibility(self) -> float:
"""Return the visibility."""
return cast(float, self.coordinator.data["Visibility"][API_METRIC][ATTR_VALUE])
return cast(
float,
self.observation_coordinator.data["Visibility"][API_METRIC][ATTR_VALUE],
)
@property
def uv_index(self) -> float:
"""Return the UV index."""
return cast(float, self.coordinator.data["UVIndex"])
return cast(float, self.observation_coordinator.data["UVIndex"])
@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
return [
{
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
@ -175,5 +207,5 @@ class AccuWeatherEntity(
ATTR_FORECAST_WIND_BEARING: item["WindDay"][ATTR_DIRECTION]["Degrees"],
ATTR_FORECAST_CONDITION: CONDITION_MAP.get(item["IconDay"]),
}
for item in self.coordinator.data[ATTR_FORECAST]
for item in self.daily_coordinator.data
]

View file

@ -11,14 +11,8 @@ from tests.common import (
)
async def init_integration(
hass, forecast=False, unsupported_icon=False
) -> MockConfigEntry:
async def init_integration(hass, unsupported_icon=False) -> MockConfigEntry:
"""Set up the AccuWeather integration in Home Assistant."""
options = {}
if forecast:
options["forecast"] = True
entry = MockConfigEntry(
domain=DOMAIN,
title="Home",
@ -29,7 +23,6 @@ async def init_integration(
"longitude": 122.12,
"name": "Home",
},
options=options,
)
current = load_json_object_fixture("accuweather/current_conditions_data.json")

View file

@ -7,7 +7,7 @@
'longitude': '**REDACTED**',
'name': 'Home',
}),
'coordinator_data': dict({
'observation_data': dict({
'ApparentTemperature': dict({
'Imperial': dict({
'Unit': 'F',
@ -297,8 +297,6 @@
}),
}),
}),
'forecast': list([
]),
}),
})
# ---

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
"""Define tests for the AccuWeather config flow."""
from unittest.mock import PropertyMock, patch
from unittest.mock import patch
from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError
from homeassistant.components.accuweather.const import CONF_FORECAST, DOMAIN
from homeassistant.components.accuweather.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant
@ -140,52 +140,3 @@ async def test_create_entry(hass: HomeAssistant) -> None:
assert result["data"][CONF_LATITUDE] == 55.55
assert result["data"][CONF_LONGITUDE] == 122.12
assert result["data"][CONF_API_KEY] == "32-character-string-1234567890qw"
async def test_options_flow(hass: HomeAssistant) -> None:
"""Test config flow options."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="123456",
data=VALID_CONFIG,
)
config_entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.accuweather.AccuWeather._async_get_data",
return_value=load_json_object_fixture("accuweather/location_data.json"),
),
patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
return_value=load_json_object_fixture(
"accuweather/current_conditions_data.json"
),
),
patch(
"homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast"
),
patch(
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
new_callable=PropertyMock,
return_value=10,
),
):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={CONF_FORECAST: True}
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert config_entry.options == {CONF_FORECAST: True}
await hass.async_block_till_done()
assert await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()

View file

@ -6,7 +6,6 @@ from homeassistant.core import HomeAssistant
from . import init_integration
from tests.common import load_json_object_fixture
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
@ -19,12 +18,6 @@ async def test_entry_diagnostics(
"""Test config entry diagnostics."""
entry = await init_integration(hass)
coordinator_data = load_json_object_fixture(
"current_conditions_data.json", "accuweather"
)
coordinator_data["forecast"] = []
result = await get_diagnostics_for_config_entry(hass, hass_client, entry)
assert result == snapshot

View file

@ -1,11 +1,14 @@
"""Test init of AccuWeather integration."""
from datetime import timedelta
from unittest.mock import patch
from accuweather import ApiError
from homeassistant.components.accuweather.const import DOMAIN
from homeassistant.components.accuweather.const import (
DOMAIN,
UPDATE_INTERVAL_DAILY_FORECAST,
UPDATE_INTERVAL_OBSERVATION,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_PLATFORM
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE
@ -76,30 +79,8 @@ async def test_update_interval(hass: HomeAssistant) -> None:
assert entry.state is ConfigEntryState.LOADED
current = load_json_object_fixture("accuweather/current_conditions_data.json")
future = utcnow() + timedelta(minutes=40)
with patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
return_value=current,
) as mock_current:
assert mock_current.call_count == 0
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
assert mock_current.call_count == 1
async def test_update_interval_forecast(hass: HomeAssistant) -> None:
"""Test correct update interval when forecast is True."""
entry = await init_integration(hass, forecast=True)
assert entry.state is ConfigEntryState.LOADED
current = load_json_object_fixture("accuweather/current_conditions_data.json")
forecast = load_json_array_fixture("accuweather/forecast_data.json")
future = utcnow() + timedelta(minutes=80)
with (
patch(
@ -114,10 +95,14 @@ async def test_update_interval_forecast(hass: HomeAssistant) -> None:
assert mock_current.call_count == 0
assert mock_forecast.call_count == 0
async_fire_time_changed(hass, future)
async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_OBSERVATION)
await hass.async_block_till_done()
assert mock_current.call_count == 1
async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_DAILY_FORECAST)
await hass.async_block_till_done()
assert mock_forecast.call_count == 1

View file

@ -3,29 +3,20 @@
from datetime import timedelta
from unittest.mock import PropertyMock, patch
from homeassistant.components.accuweather.const import ATTRIBUTION
from homeassistant.components.sensor import (
ATTR_OPTIONS,
ATTR_STATE_CLASS,
SensorDeviceClass,
SensorStateClass,
)
from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError
from aiohttp.client_exceptions import ClientConnectorError
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.accuweather.const import UPDATE_INTERVAL_DAILY_FORECAST
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
ATTR_ENTITY_ID,
ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT,
CONCENTRATION_PARTS_PER_CUBIC_METER,
PERCENTAGE,
STATE_UNAVAILABLE,
UV_INDEX,
UnitOfIrradiance,
Platform,
UnitOfLength,
UnitOfSpeed,
UnitOfTemperature,
UnitOfTime,
UnitOfVolumetricFlux,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@ -42,517 +33,23 @@ from tests.common import (
)
async def test_sensor_without_forecast(
async def test_sensor(
hass: HomeAssistant,
entity_registry_enabled_by_default: None,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test states of the sensor without forecast."""
await init_integration(hass)
"""Test states of the sensor."""
with patch("homeassistant.components.accuweather.PLATFORMS", [Platform.SENSOR]):
entry = await init_integration(hass)
state = hass.states.get("sensor.home_cloud_ceiling")
assert state
assert state.state == "3200.0"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:weather-fog"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfLength.METERS
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.DISTANCE
entity_entries = er.async_entries_for_config_entry(entity_registry, entry.entry_id)
entry = entity_registry.async_get("sensor.home_cloud_ceiling")
assert entry
assert entry.unique_id == "0123456-ceiling"
assert entry.options["sensor"] == {"suggested_display_precision": 0}
state = hass.states.get("sensor.home_precipitation")
assert state
assert state.state == "0.0"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR
)
assert state.attributes.get(ATTR_ICON) is None
assert state.attributes.get("type") is None
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
assert (
state.attributes.get(ATTR_DEVICE_CLASS)
== SensorDeviceClass.PRECIPITATION_INTENSITY
)
entry = entity_registry.async_get("sensor.home_precipitation")
assert entry
assert entry.unique_id == "0123456-precipitation"
state = hass.states.get("sensor.home_pressure_tendency")
assert state
assert state.state == "falling"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:gauge"
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENUM
assert state.attributes.get(ATTR_STATE_CLASS) is None
assert state.attributes.get(ATTR_OPTIONS) == ["falling", "rising", "steady"]
entry = entity_registry.async_get("sensor.home_pressure_tendency")
assert entry
assert entry.unique_id == "0123456-pressuretendency"
assert entry.translation_key == "pressure_tendency"
state = hass.states.get("sensor.home_realfeel_temperature")
assert state
assert state.state == "25.1"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = entity_registry.async_get("sensor.home_realfeel_temperature")
assert entry
assert entry.unique_id == "0123456-realfeeltemperature"
state = hass.states.get("sensor.home_uv_index")
assert state
assert state.state == "6"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UV_INDEX
assert state.attributes.get("level") == "High"
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = entity_registry.async_get("sensor.home_uv_index")
assert entry
assert entry.unique_id == "0123456-uvindex"
state = hass.states.get("sensor.home_apparent_temperature")
assert state
assert state.state == "22.8"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = entity_registry.async_get("sensor.home_apparent_temperature")
assert entry
assert entry.unique_id == "0123456-apparenttemperature"
state = hass.states.get("sensor.home_cloud_cover")
assert state
assert state.state == "10"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE
assert state.attributes.get(ATTR_ICON) == "mdi:weather-cloudy"
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = entity_registry.async_get("sensor.home_cloud_cover")
assert entry
assert entry.unique_id == "0123456-cloudcover"
state = hass.states.get("sensor.home_dew_point")
assert state
assert state.state == "16.2"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = entity_registry.async_get("sensor.home_dew_point")
assert entry
assert entry.unique_id == "0123456-dewpoint"
state = hass.states.get("sensor.home_realfeel_temperature_shade")
assert state
assert state.state == "21.1"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = entity_registry.async_get("sensor.home_realfeel_temperature_shade")
assert entry
assert entry.unique_id == "0123456-realfeeltemperatureshade"
state = hass.states.get("sensor.home_wet_bulb_temperature")
assert state
assert state.state == "18.6"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = entity_registry.async_get("sensor.home_wet_bulb_temperature")
assert entry
assert entry.unique_id == "0123456-wetbulbtemperature"
state = hass.states.get("sensor.home_wind_chill_temperature")
assert state
assert state.state == "22.8"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
entry = entity_registry.async_get("sensor.home_wind_chill_temperature")
assert entry
assert entry.unique_id == "0123456-windchilltemperature"
state = hass.states.get("sensor.home_wind_gust_speed")
assert state
assert state.state == "20.3"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfSpeed.KILOMETERS_PER_HOUR
)
assert state.attributes.get(ATTR_ICON) is None
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.WIND_SPEED
entry = entity_registry.async_get("sensor.home_wind_gust_speed")
assert entry
assert entry.unique_id == "0123456-windgust"
state = hass.states.get("sensor.home_wind_speed")
assert state
assert state.state == "14.5"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfSpeed.KILOMETERS_PER_HOUR
)
assert state.attributes.get(ATTR_ICON) is None
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.WIND_SPEED
entry = entity_registry.async_get("sensor.home_wind_speed")
assert entry
assert entry.unique_id == "0123456-wind"
async def test_sensor_with_forecast(
hass: HomeAssistant,
entity_registry_enabled_by_default: None,
entity_registry: er.EntityRegistry,
) -> None:
"""Test states of the sensor with forecast."""
await init_integration(hass, forecast=True)
state = hass.states.get("sensor.home_hours_of_sun_today")
assert state
assert state.state == "7.2"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:weather-partly-cloudy"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTime.HOURS
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_hours_of_sun_today")
assert entry
assert entry.unique_id == "0123456-hoursofsun-0"
state = hass.states.get("sensor.home_realfeel_temperature_max_today")
assert state
assert state.state == "29.8"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_realfeel_temperature_max_today")
assert entry
state = hass.states.get("sensor.home_realfeel_temperature_min_today")
assert state
assert state.state == "15.1"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_realfeel_temperature_min_today")
assert entry
assert entry.unique_id == "0123456-realfeeltemperaturemin-0"
state = hass.states.get("sensor.home_thunderstorm_probability_today")
assert state
assert state.state == "40"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:weather-lightning"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_thunderstorm_probability_today")
assert entry
assert entry.unique_id == "0123456-thunderstormprobabilityday-0"
state = hass.states.get("sensor.home_thunderstorm_probability_tonight")
assert state
assert state.state == "40"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:weather-lightning"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_thunderstorm_probability_tonight")
assert entry
assert entry.unique_id == "0123456-thunderstormprobabilitynight-0"
state = hass.states.get("sensor.home_uv_index_today")
assert state
assert state.state == "5"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:weather-sunny"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UV_INDEX
assert state.attributes.get("level") == "moderate"
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_uv_index_today")
assert entry
assert entry.unique_id == "0123456-uvindex-0"
state = hass.states.get("sensor.home_air_quality_today")
assert state
assert state.state == "good"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:air-filter"
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENUM
assert state.attributes.get(ATTR_OPTIONS) == [
"good",
"hazardous",
"high",
"low",
"moderate",
"unhealthy",
]
state = hass.states.get("sensor.home_cloud_cover_today")
assert state
assert state.state == "58"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE
assert state.attributes.get(ATTR_ICON) == "mdi:weather-cloudy"
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_cloud_cover_today")
assert entry
assert entry.unique_id == "0123456-cloudcoverday-0"
state = hass.states.get("sensor.home_cloud_cover_tonight")
assert state
assert state.state == "65"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE
assert state.attributes.get(ATTR_ICON) == "mdi:weather-cloudy"
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_cloud_cover_tonight")
assert entry
state = hass.states.get("sensor.home_grass_pollen_today")
assert state
assert state.state == "0"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== CONCENTRATION_PARTS_PER_CUBIC_METER
)
assert state.attributes.get("level") == "low"
assert state.attributes.get(ATTR_ICON) == "mdi:grass"
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_grass_pollen_today")
assert entry
assert entry.unique_id == "0123456-grass-0"
state = hass.states.get("sensor.home_mold_pollen_today")
assert state
assert state.state == "0"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== CONCENTRATION_PARTS_PER_CUBIC_METER
)
assert state.attributes.get("level") == "low"
assert state.attributes.get(ATTR_ICON) == "mdi:blur"
entry = entity_registry.async_get("sensor.home_mold_pollen_today")
assert entry
assert entry.unique_id == "0123456-mold-0"
state = hass.states.get("sensor.home_ragweed_pollen_today")
assert state
assert state.state == "0"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== CONCENTRATION_PARTS_PER_CUBIC_METER
)
assert state.attributes.get("level") == "low"
assert state.attributes.get(ATTR_ICON) == "mdi:sprout"
entry = entity_registry.async_get("sensor.home_ragweed_pollen_today")
assert entry
assert entry.unique_id == "0123456-ragweed-0"
state = hass.states.get("sensor.home_realfeel_temperature_shade_max_today")
assert state
assert state.state == "28.0"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get(
"sensor.home_realfeel_temperature_shade_max_today"
)
assert entry
assert entry.unique_id == "0123456-realfeeltemperatureshademax-0"
state = hass.states.get("sensor.home_realfeel_temperature_shade_min_today")
assert state
assert state.state == "15.1"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TEMPERATURE
entry = entity_registry.async_get(
"sensor.home_realfeel_temperature_shade_min_today"
)
assert entry
assert entry.unique_id == "0123456-realfeeltemperatureshademin-0"
state = hass.states.get("sensor.home_tree_pollen_today")
assert state
assert state.state == "0"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== CONCENTRATION_PARTS_PER_CUBIC_METER
)
assert state.attributes.get("level") == "low"
assert state.attributes.get(ATTR_ICON) == "mdi:tree-outline"
assert state.attributes.get(ATTR_STATE_CLASS) is None
entry = entity_registry.async_get("sensor.home_tree_pollen_today")
assert entry
assert entry.unique_id == "0123456-tree-0"
state = hass.states.get("sensor.home_wind_speed_today")
assert state
assert state.state == "13.0"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfSpeed.KILOMETERS_PER_HOUR
)
assert state.attributes.get("direction") == "SSE"
assert state.attributes.get(ATTR_ICON) is None
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.WIND_SPEED
entry = entity_registry.async_get("sensor.home_wind_speed_today")
assert entry
assert entry.unique_id == "0123456-windday-0"
state = hass.states.get("sensor.home_wind_speed_tonight")
assert state
assert state.state == "7.4"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfSpeed.KILOMETERS_PER_HOUR
)
assert state.attributes.get("direction") == "WNW"
assert state.attributes.get(ATTR_ICON) is None
assert state.attributes.get(ATTR_STATE_CLASS) is None
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.WIND_SPEED
entry = entity_registry.async_get("sensor.home_wind_speed_tonight")
assert entry
assert entry.unique_id == "0123456-windnight-0"
state = hass.states.get("sensor.home_wind_gust_speed_today")
assert state
assert state.state == "29.6"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfSpeed.KILOMETERS_PER_HOUR
)
assert state.attributes.get("direction") == "S"
assert state.attributes.get(ATTR_ICON) is None
assert state.attributes.get(ATTR_STATE_CLASS) is None
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.WIND_SPEED
entry = entity_registry.async_get("sensor.home_wind_gust_speed_today")
assert entry
assert entry.unique_id == "0123456-windgustday-0"
state = hass.states.get("sensor.home_wind_gust_speed_tonight")
assert state
assert state.state == "18.5"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfSpeed.KILOMETERS_PER_HOUR
)
assert state.attributes.get("direction") == "WSW"
assert state.attributes.get(ATTR_ICON) is None
assert state.attributes.get(ATTR_STATE_CLASS) is None
assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.WIND_SPEED
entry = entity_registry.async_get("sensor.home_wind_gust_speed_tonight")
assert entry
assert entry.unique_id == "0123456-windgustnight-0"
entry = entity_registry.async_get("sensor.home_air_quality_today")
assert entry
assert entry.unique_id == "0123456-airquality-0"
state = hass.states.get("sensor.home_solar_irradiance_today")
assert state
assert state.state == "7447.1"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:weather-sunny"
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfIrradiance.WATTS_PER_SQUARE_METER
)
entry = entity_registry.async_get("sensor.home_solar_irradiance_today")
assert entry
assert entry.unique_id == "0123456-solarirradianceday-0"
state = hass.states.get("sensor.home_solar_irradiance_tonight")
assert state
assert state.state == "271.6"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
assert state.attributes.get(ATTR_ICON) == "mdi:weather-sunny"
assert (
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
== UnitOfIrradiance.WATTS_PER_SQUARE_METER
)
entry = entity_registry.async_get("sensor.home_solar_irradiance_tonight")
assert entry
assert entry.unique_id == "0123456-solarirradiancenight-0"
state = hass.states.get("sensor.home_condition_today")
assert state
assert (
state.state
== "Clouds and sunshine with a couple of showers and a thunderstorm around late this afternoon"
)
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
entry = entity_registry.async_get("sensor.home_condition_today")
assert entry
assert entry.unique_id == "0123456-longphraseday-0"
state = hass.states.get("sensor.home_condition_tonight")
assert state
assert state.state == "Partly cloudy"
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
entry = entity_registry.async_get("sensor.home_condition_tonight")
assert entry
assert entry.unique_id == "0123456-longphrasenight-0"
assert entity_entries
for entity_entry in entity_entries:
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
assert (state := hass.states.get(entity_entry.entity_id))
assert state == snapshot(name=f"{entity_entry.entity_id}-state")
async def test_availability(hass: HomeAssistant) -> None:
@ -599,24 +96,88 @@ async def test_availability(hass: HomeAssistant) -> None:
assert state.state == "3200.0"
@pytest.mark.parametrize(
"exception",
[
ApiError,
ConnectionError,
ClientConnectorError,
InvalidApiKeyError,
RequestsExceededError,
],
)
async def test_availability_forecast(hass: HomeAssistant, exception: Exception) -> None:
"""Ensure that we mark the entities unavailable correctly when service is offline."""
current = load_json_object_fixture("accuweather/current_conditions_data.json")
forecast = load_json_array_fixture("accuweather/forecast_data.json")
entity_id = "sensor.home_hours_of_sun_day_2"
await init_integration(hass)
state = hass.states.get(entity_id)
assert state
assert state.state != STATE_UNAVAILABLE
assert state.state == "5.7"
with (
patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
return_value=current,
),
patch(
"homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast",
side_effect=exception,
),
patch(
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
new_callable=PropertyMock,
return_value=10,
),
):
async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_DAILY_FORECAST)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == STATE_UNAVAILABLE
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,
),
):
async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_DAILY_FORECAST * 2)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state != STATE_UNAVAILABLE
assert state.state == "5.7"
async def test_manual_update_entity(hass: HomeAssistant) -> None:
"""Test manual update entity via service homeassistant/update_entity."""
await init_integration(hass, forecast=True)
await init_integration(hass)
await async_setup_component(hass, "homeassistant", {})
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,
) as mock_current,
patch(
"homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast",
return_value=forecast,
) as mock_forecast,
patch(
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
new_callable=PropertyMock,
@ -629,8 +190,7 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None:
{ATTR_ENTITY_ID: ["sensor.home_cloud_ceiling"]},
blocking=True,
)
assert mock_current.call_count == 1
assert mock_forecast.call_count == 1
assert mock_current.call_count == 1
async def test_sensor_imperial_units(hass: HomeAssistant) -> None:

View file

@ -7,7 +7,10 @@ from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.accuweather.const import ATTRIBUTION
from homeassistant.components.accuweather.const import (
ATTRIBUTION,
UPDATE_INTERVAL_DAILY_FORECAST,
)
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_WEATHER_APPARENT_TEMPERATURE,
@ -24,6 +27,7 @@ from homeassistant.components.weather import (
DOMAIN as WEATHER_DOMAIN,
LEGACY_SERVICE_GET_FORECAST,
SERVICE_GET_FORECASTS,
WeatherEntityFeature,
)
from homeassistant.const import (
ATTR_ATTRIBUTION,
@ -65,7 +69,10 @@ async def test_weather(hass: HomeAssistant, entity_registry: er.EntityRegistry)
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 ATTR_SUPPORTED_FEATURES not in state.attributes
assert (
state.attributes.get(ATTR_SUPPORTED_FEATURES)
is WeatherEntityFeature.FORECAST_DAILY
)
entry = entity_registry.async_get("weather.home")
assert entry
@ -118,22 +125,17 @@ async def test_availability(hass: HomeAssistant) -> None:
async def test_manual_update_entity(hass: HomeAssistant) -> None:
"""Test manual update entity via service homeassistant/update_entity."""
await init_integration(hass, forecast=True)
await init_integration(hass)
await async_setup_component(hass, "homeassistant", {})
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,
) as mock_current,
patch(
"homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast",
return_value=forecast,
) as mock_forecast,
patch(
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
new_callable=PropertyMock,
@ -147,12 +149,11 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None:
blocking=True,
)
assert mock_current.call_count == 1
assert mock_forecast.call_count == 1
async def test_unsupported_condition_icon_data(hass: HomeAssistant) -> None:
"""Test with unsupported condition icon data."""
await init_integration(hass, forecast=True, unsupported_icon=True)
await init_integration(hass, unsupported_icon=True)
state = hass.states.get("weather.home")
assert state.attributes.get(ATTR_FORECAST_CONDITION) is None
@ -171,7 +172,7 @@ async def test_forecast_service(
service: str,
) -> None:
"""Test multiple forecast."""
await init_integration(hass, forecast=True)
await init_integration(hass)
response = await hass.services.async_call(
WEATHER_DOMAIN,
@ -195,7 +196,7 @@ async def test_forecast_subscription(
"""Test multiple forecast."""
client = await hass_ws_client(hass)
await init_integration(hass, forecast=True)
await init_integration(hass)
await client.send_json_auto_id(
{
@ -235,7 +236,7 @@ async def test_forecast_subscription(
return_value=10,
),
):
freezer.tick(timedelta(minutes=80) + timedelta(seconds=1))
freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST + timedelta(seconds=1))
await hass.async_block_till_done()
msg = await client.receive_json()