1
0
mirror of https://github.com/home-assistant/core.git synced 2025-05-14 10:59:15 +00:00

Address weatherkit late review comments ()

* Address review comments from original weatherkit PR

* Use .get() for optional fields
This commit is contained in:
TJ Horner 2023-09-13 00:22:58 -07:00 committed by GitHub
parent e87603aa59
commit dd95b51d10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 87 deletions
homeassistant/components/weatherkit
tests/components/weatherkit

@ -120,7 +120,7 @@ class WeatherKitFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
location[CONF_LONGITUDE], location[CONF_LONGITUDE],
) )
if len(availability) == 0: if not availability:
raise WeatherKitUnsupportedLocationError( raise WeatherKitUnsupportedLocationError(
"API does not support this location" "API does not support this location"
) )

@ -5,7 +5,10 @@ LOGGER: Logger = getLogger(__package__)
NAME = "Apple WeatherKit" NAME = "Apple WeatherKit"
DOMAIN = "weatherkit" DOMAIN = "weatherkit"
ATTRIBUTION = "Data provided by Apple Weather. https://developer.apple.com/weatherkit/data-source-attribution/" ATTRIBUTION = (
"Data provided by Apple Weather. "
"https://developer.apple.com/weatherkit/data-source-attribution/"
)
CONF_KEY_ID = "key_id" CONF_KEY_ID = "key_id"
CONF_SERVICE_ID = "service_id" CONF_SERVICE_ID = "service_id"

@ -5,6 +5,18 @@ from typing import Any, cast
from apple_weatherkit import DataSetType from apple_weatherkit import DataSetType
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLOUDY,
ATTR_CONDITION_EXCEPTIONAL,
ATTR_CONDITION_FOG,
ATTR_CONDITION_HAIL,
ATTR_CONDITION_LIGHTNING,
ATTR_CONDITION_PARTLYCLOUDY,
ATTR_CONDITION_POURING,
ATTR_CONDITION_RAINY,
ATTR_CONDITION_SNOWY,
ATTR_CONDITION_SNOWY_RAINY,
ATTR_CONDITION_SUNNY,
ATTR_CONDITION_WINDY,
Forecast, Forecast,
SingleCoordinatorWeatherEntity, SingleCoordinatorWeatherEntity,
WeatherEntityFeature, WeatherEntityFeature,
@ -40,71 +52,71 @@ async def async_setup_entry(
condition_code_to_hass = { condition_code_to_hass = {
"BlowingDust": "windy", "BlowingDust": ATTR_CONDITION_WINDY,
"Clear": "sunny", "Clear": ATTR_CONDITION_SUNNY,
"Cloudy": "cloudy", "Cloudy": ATTR_CONDITION_CLOUDY,
"Foggy": "fog", "Foggy": ATTR_CONDITION_FOG,
"Haze": "fog", "Haze": ATTR_CONDITION_FOG,
"MostlyClear": "sunny", "MostlyClear": ATTR_CONDITION_SUNNY,
"MostlyCloudy": "cloudy", "MostlyCloudy": ATTR_CONDITION_CLOUDY,
"PartlyCloudy": "partlycloudy", "PartlyCloudy": ATTR_CONDITION_PARTLYCLOUDY,
"Smoky": "fog", "Smoky": ATTR_CONDITION_FOG,
"Breezy": "windy", "Breezy": ATTR_CONDITION_WINDY,
"Windy": "windy", "Windy": ATTR_CONDITION_WINDY,
"Drizzle": "rainy", "Drizzle": ATTR_CONDITION_RAINY,
"HeavyRain": "pouring", "HeavyRain": ATTR_CONDITION_POURING,
"IsolatedThunderstorms": "lightning", "IsolatedThunderstorms": ATTR_CONDITION_LIGHTNING,
"Rain": "rainy", "Rain": ATTR_CONDITION_RAINY,
"SunShowers": "rainy", "SunShowers": ATTR_CONDITION_RAINY,
"ScatteredThunderstorms": "lightning", "ScatteredThunderstorms": ATTR_CONDITION_LIGHTNING,
"StrongStorms": "lightning", "StrongStorms": ATTR_CONDITION_LIGHTNING,
"Thunderstorms": "lightning", "Thunderstorms": ATTR_CONDITION_LIGHTNING,
"Frigid": "snowy", "Frigid": ATTR_CONDITION_SNOWY,
"Hail": "hail", "Hail": ATTR_CONDITION_HAIL,
"Hot": "sunny", "Hot": ATTR_CONDITION_SUNNY,
"Flurries": "snowy", "Flurries": ATTR_CONDITION_SNOWY,
"Sleet": "snowy", "Sleet": ATTR_CONDITION_SNOWY,
"Snow": "snowy", "Snow": ATTR_CONDITION_SNOWY,
"SunFlurries": "snowy", "SunFlurries": ATTR_CONDITION_SNOWY,
"WintryMix": "snowy", "WintryMix": ATTR_CONDITION_SNOWY,
"Blizzard": "snowy", "Blizzard": ATTR_CONDITION_SNOWY,
"BlowingSnow": "snowy", "BlowingSnow": ATTR_CONDITION_SNOWY,
"FreezingDrizzle": "snowy-rainy", "FreezingDrizzle": ATTR_CONDITION_SNOWY_RAINY,
"FreezingRain": "snowy-rainy", "FreezingRain": ATTR_CONDITION_SNOWY_RAINY,
"HeavySnow": "snowy", "HeavySnow": ATTR_CONDITION_SNOWY,
"Hurricane": "exceptional", "Hurricane": ATTR_CONDITION_EXCEPTIONAL,
"TropicalStorm": "exceptional", "TropicalStorm": ATTR_CONDITION_EXCEPTIONAL,
} }
def _map_daily_forecast(forecast) -> Forecast: def _map_daily_forecast(forecast: dict[str, Any]) -> Forecast:
return { return {
"datetime": forecast.get("forecastStart"), "datetime": forecast["forecastStart"],
"condition": condition_code_to_hass[forecast.get("conditionCode")], "condition": condition_code_to_hass[forecast["conditionCode"]],
"native_temperature": forecast.get("temperatureMax"), "native_temperature": forecast["temperatureMax"],
"native_templow": forecast.get("temperatureMin"), "native_templow": forecast["temperatureMin"],
"native_precipitation": forecast.get("precipitationAmount"), "native_precipitation": forecast["precipitationAmount"],
"precipitation_probability": forecast.get("precipitationChance") * 100, "precipitation_probability": forecast["precipitationChance"] * 100,
"uv_index": forecast.get("maxUvIndex"), "uv_index": forecast["maxUvIndex"],
} }
def _map_hourly_forecast(forecast) -> Forecast: def _map_hourly_forecast(forecast: dict[str, Any]) -> Forecast:
return { return {
"datetime": forecast.get("forecastStart"), "datetime": forecast["forecastStart"],
"condition": condition_code_to_hass[forecast.get("conditionCode")], "condition": condition_code_to_hass[forecast["conditionCode"]],
"native_temperature": forecast.get("temperature"), "native_temperature": forecast["temperature"],
"native_apparent_temperature": forecast.get("temperatureApparent"), "native_apparent_temperature": forecast["temperatureApparent"],
"native_dew_point": forecast.get("temperatureDewPoint"), "native_dew_point": forecast.get("temperatureDewPoint"),
"native_pressure": forecast.get("pressure"), "native_pressure": forecast["pressure"],
"native_wind_gust_speed": forecast.get("windGust"), "native_wind_gust_speed": forecast.get("windGust"),
"native_wind_speed": forecast.get("windSpeed"), "native_wind_speed": forecast["windSpeed"],
"wind_bearing": forecast.get("windDirection"), "wind_bearing": forecast.get("windDirection"),
"humidity": forecast.get("humidity") * 100, "humidity": forecast["humidity"] * 100,
"native_precipitation": forecast.get("precipitationAmount"), "native_precipitation": forecast.get("precipitationAmount"),
"precipitation_probability": forecast.get("precipitationChance") * 100, "precipitation_probability": forecast["precipitationChance"] * 100,
"cloud_coverage": forecast.get("cloudCover") * 100, "cloud_coverage": forecast["cloudCover"] * 100,
"uv_index": forecast.get("uvIndex"), "uv_index": forecast["uvIndex"],
} }
@ -142,10 +154,11 @@ class WeatherKitWeather(
@property @property
def supported_features(self) -> WeatherEntityFeature: def supported_features(self) -> WeatherEntityFeature:
"""Determine supported features based on available data sets reported by WeatherKit.""" """Determine supported features based on available data sets reported by WeatherKit."""
if not self.coordinator.supported_data_sets:
return WeatherEntityFeature(0)
features = WeatherEntityFeature(0) features = WeatherEntityFeature(0)
if not self.coordinator.supported_data_sets:
return features
if DataSetType.DAILY_FORECAST in self.coordinator.supported_data_sets: if DataSetType.DAILY_FORECAST in self.coordinator.supported_data_sets:
features |= WeatherEntityFeature.FORECAST_DAILY features |= WeatherEntityFeature.FORECAST_DAILY
if DataSetType.HOURLY_FORECAST in self.coordinator.supported_data_sets: if DataSetType.HOURLY_FORECAST in self.coordinator.supported_data_sets:

@ -40,26 +40,6 @@ EXAMPLE_USER_INPUT = {
} }
async def _test_exception_generates_error(
hass: HomeAssistant, exception: Exception, error: str
) -> None:
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.weatherkit.WeatherKitApiClient.get_availability",
side_effect=exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
EXAMPLE_USER_INPUT,
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": error}
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we get the form and create an entry.""" """Test we get the form and create an entry."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -69,8 +49,8 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
assert result["errors"] == {} assert result["errors"] == {}
with patch( with patch(
"homeassistant.components.weatherkit.config_flow.WeatherKitFlowHandler._test_config", "homeassistant.components.weatherkit.WeatherKitApiClient.get_availability",
return_value=None, return_value=[DataSetType.CURRENT_WEATHER],
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -100,7 +80,21 @@ async def test_error_handling(
hass: HomeAssistant, exception: Exception, expected_error: str hass: HomeAssistant, exception: Exception, expected_error: str
) -> None: ) -> None:
"""Test that we handle various exceptions and generate appropriate errors.""" """Test that we handle various exceptions and generate appropriate errors."""
await _test_exception_generates_error(hass, exception, expected_error) result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.weatherkit.WeatherKitApiClient.get_availability",
side_effect=exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
EXAMPLE_USER_INPUT,
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": expected_error}
async def test_form_unsupported_location(hass: HomeAssistant) -> None: async def test_form_unsupported_location(hass: HomeAssistant) -> None:

@ -5,13 +5,10 @@ from apple_weatherkit.client import (
WeatherKitApiClientAuthenticationError, WeatherKitApiClientAuthenticationError,
WeatherKitApiClientError, WeatherKitApiClientError,
) )
import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.weatherkit import async_setup_entry
from homeassistant.components.weatherkit.const import DOMAIN from homeassistant.components.weatherkit.const import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from . import EXAMPLE_CONFIG_DATA from . import EXAMPLE_CONFIG_DATA
@ -50,7 +47,7 @@ async def test_client_error_handling(hass: HomeAssistant) -> None:
data=EXAMPLE_CONFIG_DATA, data=EXAMPLE_CONFIG_DATA,
) )
with pytest.raises(ConfigEntryNotReady), patch( with patch(
"homeassistant.components.weatherkit.WeatherKitApiClient.get_weather_data", "homeassistant.components.weatherkit.WeatherKitApiClient.get_weather_data",
side_effect=WeatherKitApiClientError, side_effect=WeatherKitApiClientError,
), patch( ), patch(
@ -58,6 +55,7 @@ async def test_client_error_handling(hass: HomeAssistant) -> None:
side_effect=WeatherKitApiClientError, side_effect=WeatherKitApiClientError,
): ):
entry.add_to_hass(hass) entry.add_to_hass(hass)
config_entries.current_entry.set(entry) await hass.config_entries.async_setup(entry.entry_id)
await async_setup_entry(hass, entry)
await hass.async_block_till_done() await hass.async_block_till_done()
assert entry.state == config_entries.ConfigEntryState.SETUP_RETRY