From c3c9ed6835fe65e8053149ec866323bce54f9419 Mon Sep 17 00:00:00 2001 From: Sean Chen Date: Mon, 16 Jan 2023 15:19:11 -0600 Subject: [PATCH] Improve type hints in nws (#83173) --- homeassistant/components/nws/__init__.py | 4 +- homeassistant/components/nws/config_flow.py | 14 ++++- homeassistant/components/nws/const.py | 2 +- homeassistant/components/nws/sensor.py | 14 +++-- homeassistant/components/nws/weather.py | 62 ++++++++++++++------- 5 files changed, 65 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index 051402af9fe..fb8f9bf0c76 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -37,7 +37,7 @@ FAILED_SCAN_INTERVAL = datetime.timedelta(minutes=1) DEBOUNCE_TIME = 60 # in seconds -def base_unique_id(latitude, longitude): +def base_unique_id(latitude: float, longitude: float) -> str: """Return unique id for entries in configuration.""" return f"{latitude}_{longitude}" @@ -174,7 +174,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -def device_info(latitude, longitude) -> DeviceInfo: +def device_info(latitude: float, longitude: float) -> DeviceInfo: """Return device registry information.""" return DeviceInfo( entry_type=DeviceEntryType.SERVICE, diff --git a/homeassistant/components/nws/config_flow.py b/homeassistant/components/nws/config_flow.py index 517062e23d8..10eab390917 100644 --- a/homeassistant/components/nws/config_flow.py +++ b/homeassistant/components/nws/config_flow.py @@ -1,5 +1,8 @@ """Config flow for National Weather Service (NWS) integration.""" +from __future__ import annotations + import logging +from typing import Any import aiohttp from pynws import SimpleNWS @@ -7,6 +10,7 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -16,7 +20,9 @@ from .const import CONF_STATION, DOMAIN _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input( + hass: core.HomeAssistant, data: dict[str, Any] +) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -44,9 +50,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Handle the initial step.""" - errors = {} + errors: dict[str, str] = {} if user_input is not None: await self.async_set_unique_id( base_unique_id(user_input[CONF_LATITUDE], user_input[CONF_LONGITUDE]) diff --git a/homeassistant/components/nws/const.py b/homeassistant/components/nws/const.py index 7a07c1cf7c0..96844edd800 100644 --- a/homeassistant/components/nws/const.py +++ b/homeassistant/components/nws/const.py @@ -28,7 +28,7 @@ ATTRIBUTION = "Data from National Weather Service/NOAA" ATTR_FORECAST_DETAILED_DESCRIPTION = "detailed_description" ATTR_FORECAST_DAYTIME = "daytime" -CONDITION_CLASSES = { +CONDITION_CLASSES: dict[str, list[str]] = { ATTR_CONDITION_EXCEPTIONAL: [ "Tornado", "Hurricane conditions", diff --git a/homeassistant/components/nws/sensor.py b/homeassistant/components/nws/sensor.py index 06d29c830a0..61f823de8e6 100644 --- a/homeassistant/components/nws/sensor.py +++ b/homeassistant/components/nws/sensor.py @@ -2,6 +2,8 @@ from __future__ import annotations from dataclasses import dataclass +from types import MappingProxyType +from typing import Any from pynws import SimpleNWS @@ -174,11 +176,11 @@ class NWSSensor(CoordinatorEntity[NwsDataUpdateCoordinator], SensorEntity): def __init__( self, hass: HomeAssistant, - entry_data, - hass_data, + entry_data: MappingProxyType[str, Any], + hass_data: dict[str, Any], description: NWSSensorEntityDescription, - station, - ): + station: str, + ) -> None: """Initialise the platform with a data instance.""" super().__init__(hass_data[COORDINATOR_OBSERVATION]) self._nws: SimpleNWS = hass_data[NWS_DATA] @@ -191,7 +193,7 @@ class NWSSensor(CoordinatorEntity[NwsDataUpdateCoordinator], SensorEntity): self._attr_native_unit_of_measurement = description.unit_convert @property - def native_value(self): + def native_value(self) -> float | None: """Return the state.""" value = self._nws.observation.get(self.entity_description.key) if value is None: @@ -224,7 +226,7 @@ class NWSSensor(CoordinatorEntity[NwsDataUpdateCoordinator], SensorEntity): return value @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique_id for this entity.""" return f"{base_unique_id(self._latitude, self._longitude)}_{self.entity_description.key}" diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 341f35353ef..b7982247ab2 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -1,4 +1,9 @@ """Support for NWS weather service.""" +from __future__ import annotations + +from types import MappingProxyType +from typing import TYPE_CHECKING, Any + from homeassistant.components.weather import ( ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_SUNNY, @@ -8,6 +13,7 @@ from homeassistant.components.weather import ( ATTR_FORECAST_PRECIPITATION_PROBABILITY, ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_BEARING, + Forecast, WeatherEntity, ) from homeassistant.config_entries import ConfigEntry @@ -24,6 +30,7 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.dt import utcnow from homeassistant.util.unit_conversion import SpeedConverter, TemperatureConverter +from homeassistant.util.unit_system import UnitSystem from . import base_unique_id, device_info from .const import ( @@ -45,14 +52,16 @@ from .const import ( PARALLEL_UPDATES = 0 -def convert_condition(time, weather): +def convert_condition( + time: str, weather: tuple[tuple[str, int | None], ...] +) -> tuple[str, int | None]: """ Convert NWS codes to HA condition. Choose first condition in CONDITION_CLASSES that exists in weather code. If no match is found, return first condition from NWS """ - conditions = [w[0] for w in weather] + conditions: list[str] = [w[0] for w in weather] prec_probs = [w[1] or 0 for w in weather] # Choose condition with highest priority. @@ -88,12 +97,27 @@ async def async_setup_entry( ) +if TYPE_CHECKING: + + class NWSForecast(Forecast): + """Forecast with extra fields needed for NWS.""" + + detailed_description: str | None + daytime: bool | None + + class NWSWeather(WeatherEntity): """Representation of a weather condition.""" _attr_should_poll = False - def __init__(self, entry_data, hass_data, mode, units): + def __init__( + self, + entry_data: MappingProxyType[str, Any], + hass_data: dict[str, Any], + mode: str, + units: UnitSystem, + ) -> None: """Initialise the platform with a data instance and station name.""" self.nws = hass_data[NWS_DATA] self.latitude = entry_data[CONF_LATITUDE] @@ -132,67 +156,67 @@ class NWSWeather(WeatherEntity): self.async_write_ha_state() @property - def attribution(self): + def attribution(self) -> str: """Return the attribution.""" return ATTRIBUTION @property - def name(self): + def name(self) -> str: """Return the name of the station.""" return f"{self.station} {self.mode.title()}" @property - def native_temperature(self): + def native_temperature(self) -> float | None: """Return the current temperature.""" if self.observation: return self.observation.get("temperature") return None @property - def native_temperature_unit(self): + def native_temperature_unit(self) -> str: """Return the current temperature unit.""" return UnitOfTemperature.CELSIUS @property - def native_pressure(self): + def native_pressure(self) -> int | None: """Return the current pressure.""" if self.observation: return self.observation.get("seaLevelPressure") return None @property - def native_pressure_unit(self): + def native_pressure_unit(self) -> str: """Return the current pressure unit.""" return UnitOfPressure.PA @property - def humidity(self): + def humidity(self) -> float | None: """Return the name of the sensor.""" if self.observation: return self.observation.get("relativeHumidity") return None @property - def native_wind_speed(self): + def native_wind_speed(self) -> float | None: """Return the current windspeed.""" if self.observation: return self.observation.get("windSpeed") return None @property - def native_wind_speed_unit(self): + def native_wind_speed_unit(self) -> str: """Return the current windspeed.""" return UnitOfSpeed.KILOMETERS_PER_HOUR @property - def wind_bearing(self): + def wind_bearing(self) -> int | None: """Return the current wind bearing (degrees).""" if self.observation: return self.observation.get("windDirection") return None @property - def condition(self): + def condition(self) -> str | None: """Return current condition.""" weather = None if self.observation: @@ -205,23 +229,23 @@ class NWSWeather(WeatherEntity): return None @property - def native_visibility(self): + def native_visibility(self) -> int | None: """Return visibility.""" if self.observation: return self.observation.get("visibility") return None @property - def native_visibility_unit(self): + def native_visibility_unit(self) -> str: """Return visibility unit.""" return UnitOfLength.METERS @property - def forecast(self): + def forecast(self) -> list[Forecast] | None: """Return forecast.""" if self._forecast is None: return None - forecast = [] + forecast: list[NWSForecast] = [] for forecast_entry in self._forecast: data = { ATTR_FORECAST_DETAILED_DESCRIPTION: forecast_entry.get( @@ -262,7 +286,7 @@ class NWSWeather(WeatherEntity): return forecast @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique_id for this entity.""" return f"{base_unique_id(self.latitude, self.longitude)}_{self.mode}"