From d98e580c3c4e30fe07e16b763ed2256dc0e8dc16 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 22 Jul 2021 07:24:07 +0200 Subject: [PATCH] Use NamedTuple - nws (#53293) --- homeassistant/components/nws/const.py | 176 +++++++++++++------------ homeassistant/components/nws/sensor.py | 69 +++------- tests/components/nws/test_sensor.py | 23 ++-- 3 files changed, 122 insertions(+), 146 deletions(-) diff --git a/homeassistant/components/nws/const.py b/homeassistant/components/nws/const.py index f82a70ea4e0..b5814613847 100644 --- a/homeassistant/components/nws/const.py +++ b/homeassistant/components/nws/const.py @@ -1,5 +1,8 @@ """Constants for National Weather Service Integration.""" +from __future__ import annotations + from datetime import timedelta +from typing import NamedTuple from homeassistant.components.weather import ( ATTR_CONDITION_CLOUDY, @@ -17,7 +20,6 @@ from homeassistant.components.weather import ( ATTR_CONDITION_WINDY_VARIANT, ) from homeassistant.const import ( - ATTR_DEVICE_CLASS, DEGREE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PRESSURE, @@ -40,11 +42,6 @@ ATTRIBUTION = "Data from National Weather Service/NOAA" ATTR_FORECAST_DETAILED_DESCRIPTION = "detailed_description" ATTR_FORECAST_DAYTIME = "daytime" -ATTR_ICON = "icon" -ATTR_LABEL = "label" -ATTR_UNIT = "unit" -ATTR_UNIT_CONVERT = "unit_convert" -ATTR_UNIT_CONVERT_METHOD = "unit_convert_method" CONDITION_CLASSES = { ATTR_CONDITION_EXCEPTIONAL: [ @@ -101,82 +98,93 @@ COORDINATOR_FORECAST_HOURLY = "coordinator_forecast_hourly" OBSERVATION_VALID_TIME = timedelta(minutes=20) FORECAST_VALID_TIME = timedelta(minutes=45) -SENSOR_TYPES = { - "dewpoint": { - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_ICON: None, - ATTR_LABEL: "Dew Point", - ATTR_UNIT: TEMP_CELSIUS, - ATTR_UNIT_CONVERT: TEMP_CELSIUS, - }, - "temperature": { - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_ICON: None, - ATTR_LABEL: "Temperature", - ATTR_UNIT: TEMP_CELSIUS, - ATTR_UNIT_CONVERT: TEMP_CELSIUS, - }, - "windChill": { - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_ICON: None, - ATTR_LABEL: "Wind Chill", - ATTR_UNIT: TEMP_CELSIUS, - ATTR_UNIT_CONVERT: TEMP_CELSIUS, - }, - "heatIndex": { - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_ICON: None, - ATTR_LABEL: "Heat Index", - ATTR_UNIT: TEMP_CELSIUS, - ATTR_UNIT_CONVERT: TEMP_CELSIUS, - }, - "relativeHumidity": { - ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY, - ATTR_ICON: None, - ATTR_LABEL: "Relative Humidity", - ATTR_UNIT: PERCENTAGE, - ATTR_UNIT_CONVERT: PERCENTAGE, - }, - "windSpeed": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:weather-windy", - ATTR_LABEL: "Wind Speed", - ATTR_UNIT: SPEED_KILOMETERS_PER_HOUR, - ATTR_UNIT_CONVERT: SPEED_MILES_PER_HOUR, - }, - "windGust": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:weather-windy", - ATTR_LABEL: "Wind Gust", - ATTR_UNIT: SPEED_KILOMETERS_PER_HOUR, - ATTR_UNIT_CONVERT: SPEED_MILES_PER_HOUR, - }, - "windDirection": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:compass-rose", - ATTR_LABEL: "Wind Direction", - ATTR_UNIT: DEGREE, - ATTR_UNIT_CONVERT: DEGREE, - }, - "barometricPressure": { - ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, - ATTR_ICON: None, - ATTR_LABEL: "Barometric Pressure", - ATTR_UNIT: PRESSURE_PA, - ATTR_UNIT_CONVERT: PRESSURE_INHG, - }, - "seaLevelPressure": { - ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, - ATTR_ICON: None, - ATTR_LABEL: "Sea Level Pressure", - ATTR_UNIT: PRESSURE_PA, - ATTR_UNIT_CONVERT: PRESSURE_INHG, - }, - "visibility": { - ATTR_DEVICE_CLASS: None, - ATTR_ICON: "mdi:eye", - ATTR_LABEL: "Visibility", - ATTR_UNIT: LENGTH_METERS, - ATTR_UNIT_CONVERT: LENGTH_MILES, - }, + +class NWSSensorMetadata(NamedTuple): + """Sensor metadata for an individual NWS sensor.""" + + label: str + icon: str | None + device_class: str | None + unit: str + unit_convert: str + + +SENSOR_TYPES: dict[str, NWSSensorMetadata] = { + "dewpoint": NWSSensorMetadata( + "Dew Point", + icon=None, + device_class=DEVICE_CLASS_TEMPERATURE, + unit=TEMP_CELSIUS, + unit_convert=TEMP_CELSIUS, + ), + "temperature": NWSSensorMetadata( + "Temperature", + icon=None, + device_class=DEVICE_CLASS_TEMPERATURE, + unit=TEMP_CELSIUS, + unit_convert=TEMP_CELSIUS, + ), + "windChill": NWSSensorMetadata( + "Wind Chill", + icon=None, + device_class=DEVICE_CLASS_TEMPERATURE, + unit=TEMP_CELSIUS, + unit_convert=TEMP_CELSIUS, + ), + "heatIndex": NWSSensorMetadata( + "Heat Index", + icon=None, + device_class=DEVICE_CLASS_TEMPERATURE, + unit=TEMP_CELSIUS, + unit_convert=TEMP_CELSIUS, + ), + "relativeHumidity": NWSSensorMetadata( + "Relative Humidity", + icon=None, + device_class=DEVICE_CLASS_HUMIDITY, + unit=PERCENTAGE, + unit_convert=PERCENTAGE, + ), + "windSpeed": NWSSensorMetadata( + "Wind Speed", + icon="mdi:weather-windy", + device_class=None, + unit=SPEED_KILOMETERS_PER_HOUR, + unit_convert=SPEED_MILES_PER_HOUR, + ), + "windGust": NWSSensorMetadata( + "Wind Gust", + icon="mdi:weather-windy", + device_class=None, + unit=SPEED_KILOMETERS_PER_HOUR, + unit_convert=SPEED_MILES_PER_HOUR, + ), + "windDirection": NWSSensorMetadata( + "Wind Direction", + icon="mdi:compass-rose", + device_class=None, + unit=DEGREE, + unit_convert=DEGREE, + ), + "barometricPressure": NWSSensorMetadata( + "Barometric Pressure", + icon=None, + device_class=DEVICE_CLASS_PRESSURE, + unit=PRESSURE_PA, + unit_convert=PRESSURE_INHG, + ), + "seaLevelPressure": NWSSensorMetadata( + "Sea Level Pressure", + icon=None, + device_class=DEVICE_CLASS_PRESSURE, + unit=PRESSURE_PA, + unit_convert=PRESSURE_INHG, + ), + "visibility": NWSSensorMetadata( + "Visibility", + icon="mdi:eye", + device_class=None, + unit=LENGTH_METERS, + unit_convert=LENGTH_MILES, + ), } diff --git a/homeassistant/components/nws/sensor.py b/homeassistant/components/nws/sensor.py index bff5cdca589..8bbf6af8057 100644 --- a/homeassistant/components/nws/sensor.py +++ b/homeassistant/components/nws/sensor.py @@ -2,7 +2,6 @@ from homeassistant.components.sensor import SensorEntity from homeassistant.const import ( ATTR_ATTRIBUTION, - ATTR_DEVICE_CLASS, CONF_LATITUDE, CONF_LONGITUDE, LENGTH_KILOMETERS, @@ -14,6 +13,7 @@ from homeassistant.const import ( SPEED_MILES_PER_HOUR, TEMP_CELSIUS, ) +from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.distance import convert as convert_distance from homeassistant.util.dt import utcnow @@ -21,10 +21,6 @@ from homeassistant.util.pressure import convert as convert_pressure from . import base_unique_id from .const import ( - ATTR_ICON, - ATTR_LABEL, - ATTR_UNIT, - ATTR_UNIT_CONVERT, ATTRIBUTION, CONF_STATION, COORDINATOR_OBSERVATION, @@ -32,6 +28,7 @@ from .const import ( NWS_DATA, OBSERVATION_VALID_TIME, SENSOR_TYPES, + NWSSensorMetadata, ) PARALLEL_UPDATES = 0 @@ -43,21 +40,15 @@ async def async_setup_entry(hass, entry, async_add_entities): station = entry.data[CONF_STATION] entities = [] - for sensor_type, sensor_data in SENSOR_TYPES.items(): - if hass.config.units.is_metric: - unit = sensor_data[ATTR_UNIT] - else: - unit = sensor_data[ATTR_UNIT_CONVERT] + for sensor_type, metadata in SENSOR_TYPES.items(): entities.append( NWSSensor( + hass, entry.data, hass_data, sensor_type, + metadata, station, - sensor_data[ATTR_LABEL], - sensor_data[ATTR_ICON], - sensor_data[ATTR_DEVICE_CLASS], - unit, ), ) @@ -69,14 +60,12 @@ class NWSSensor(CoordinatorEntity, SensorEntity): def __init__( self, + hass: HomeAssistant, entry_data, hass_data, sensor_type, + metadata: NWSSensorMetadata, station, - label, - icon, - device_class, - unit, ): """Initialise the platform with a data instance.""" super().__init__(hass_data[COORDINATOR_OBSERVATION]) @@ -84,11 +73,15 @@ class NWSSensor(CoordinatorEntity, SensorEntity): self._latitude = entry_data[CONF_LATITUDE] self._longitude = entry_data[CONF_LONGITUDE] self._type = sensor_type - self._station = station - self._label = label - self._icon = icon - self._device_class = device_class - self._unit = unit + self._metadata = metadata + + self._attr_name = f"{station} {metadata.label}" + self._attr_icon = metadata.icon + self._attr_device_class = metadata.device_class + if hass.config.units.is_metric: + self._attr_unit_of_measurement = metadata.unit + else: + self._attr_unit_of_measurement = metadata.unit_convert @property def state(self): @@ -96,43 +89,23 @@ class NWSSensor(CoordinatorEntity, SensorEntity): value = self._nws.observation.get(self._type) if value is None: return None - if self._unit == SPEED_MILES_PER_HOUR: + if self._attr_unit_of_measurement == SPEED_MILES_PER_HOUR: return round(convert_distance(value, LENGTH_KILOMETERS, LENGTH_MILES)) - if self._unit == LENGTH_MILES: + if self._attr_unit_of_measurement == LENGTH_MILES: return round(convert_distance(value, LENGTH_METERS, LENGTH_MILES)) - if self._unit == PRESSURE_INHG: + if self._attr_unit_of_measurement == PRESSURE_INHG: return round(convert_pressure(value, PRESSURE_PA, PRESSURE_INHG), 2) - if self._unit == TEMP_CELSIUS: + if self._attr_unit_of_measurement == TEMP_CELSIUS: return round(value, 1) - if self._unit == PERCENTAGE: + if self._attr_unit_of_measurement == PERCENTAGE: return round(value) return value - @property - def icon(self): - """Return the icon.""" - return self._icon - - @property - def device_class(self): - """Return the device class.""" - return self._device_class - - @property - def unit_of_measurement(self): - """Return the unit the value is expressed in.""" - return self._unit - @property def device_state_attributes(self): """Return the attribution.""" return {ATTR_ATTRIBUTION: ATTRIBUTION} - @property - def name(self): - """Return the name of the station.""" - return f"{self._station} {self._label}" - @property def unique_id(self): """Return a unique_id for this entity.""" diff --git a/tests/components/nws/test_sensor.py b/tests/components/nws/test_sensor.py index 44b181b1ec4..f5c0773380d 100644 --- a/tests/components/nws/test_sensor.py +++ b/tests/components/nws/test_sensor.py @@ -1,12 +1,7 @@ """Sensors for National Weather Service (NWS).""" import pytest -from homeassistant.components.nws.const import ( - ATTR_LABEL, - ATTRIBUTION, - DOMAIN, - SENSOR_TYPES, -) +from homeassistant.components.nws.const import ATTRIBUTION, DOMAIN, SENSOR_TYPES from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNKNOWN from homeassistant.util import slugify @@ -40,12 +35,12 @@ async def test_imperial_metric( """Test with imperial and metric units.""" registry = await hass.helpers.entity_registry.async_get_registry() - for sensor_name, sensor_data in SENSOR_TYPES.items(): + for sensor_name, metadata in SENSOR_TYPES.items(): registry.async_get_or_create( SENSOR_DOMAIN, DOMAIN, f"35_-75_{sensor_name}", - suggested_object_id=f"abc_{sensor_data[ATTR_LABEL]}", + suggested_object_id=f"abc_{metadata.label}", disabled_by=None, ) @@ -58,8 +53,8 @@ async def test_imperial_metric( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - for sensor_name, sensor_data in SENSOR_TYPES.items(): - state = hass.states.get(f"sensor.abc_{slugify(sensor_data[ATTR_LABEL])}") + for sensor_name, metadata in SENSOR_TYPES.items(): + state = hass.states.get(f"sensor.abc_{slugify(metadata.label)}") assert state assert state.state == result_observation[sensor_name] assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION @@ -72,12 +67,12 @@ async def test_none_values(hass, mock_simple_nws, no_weather): registry = await hass.helpers.entity_registry.async_get_registry() - for sensor_name, sensor_data in SENSOR_TYPES.items(): + for sensor_name, metadata in SENSOR_TYPES.items(): registry.async_get_or_create( SENSOR_DOMAIN, DOMAIN, f"35_-75_{sensor_name}", - suggested_object_id=f"abc_{sensor_data[ATTR_LABEL]}", + suggested_object_id=f"abc_{metadata.label}", disabled_by=None, ) @@ -89,7 +84,7 @@ async def test_none_values(hass, mock_simple_nws, no_weather): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - for sensor_name, sensor_data in SENSOR_TYPES.items(): - state = hass.states.get(f"sensor.abc_{slugify(sensor_data[ATTR_LABEL])}") + for sensor_name, metadata in SENSOR_TYPES.items(): + state = hass.states.get(f"sensor.abc_{slugify(metadata.label)}") assert state assert state.state == STATE_UNKNOWN