From 2fa07777cde7f5190825a25be85b67c6ca6e5d5c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 20 Aug 2021 15:54:57 +0200 Subject: [PATCH] Warn if unit_of_measurement is set on instances of SensorEntityDescription (#54867) * Add class BaseEntityDescription without unit_of_measurement * Refactor according to review comments * Tweak * Fix offending integrations * Fix offending integrations --- homeassistant/components/arlo/sensor.py | 8 +++--- homeassistant/components/goalzero/sensor.py | 24 ++++++++--------- homeassistant/components/sensor/__init__.py | 30 ++++++++++++++++----- homeassistant/components/tplink/sensor.py | 10 +++---- homeassistant/components/wemo/sensor.py | 4 +-- tests/components/sensor/test_init.py | 10 +++++++ 6 files changed, 57 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/arlo/sensor.py b/homeassistant/components/arlo/sensor.py index cc08cd133e4..57c897cfd59 100644 --- a/homeassistant/components/arlo/sensor.py +++ b/homeassistant/components/arlo/sensor.py @@ -49,7 +49,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="battery_level", name="Battery Level", - unit_of_measurement=PERCENTAGE, + native_unit_of_measurement=PERCENTAGE, device_class=DEVICE_CLASS_BATTERY, ), SensorEntityDescription( @@ -60,19 +60,19 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="temperature", name="Temperature", - unit_of_measurement=TEMP_CELSIUS, + native_unit_of_measurement=TEMP_CELSIUS, device_class=DEVICE_CLASS_TEMPERATURE, ), SensorEntityDescription( key="humidity", name="Humidity", - unit_of_measurement=PERCENTAGE, + native_unit_of_measurement=PERCENTAGE, device_class=DEVICE_CLASS_HUMIDITY, ), SensorEntityDescription( key="air_quality", name="Air Quality", - unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, + native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, icon="mdi:biohazard", ), ) diff --git a/homeassistant/components/goalzero/sensor.py b/homeassistant/components/goalzero/sensor.py index 8890c7db69c..b422b317601 100644 --- a/homeassistant/components/goalzero/sensor.py +++ b/homeassistant/components/goalzero/sensor.py @@ -39,14 +39,14 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="wattsIn", name="Watts In", device_class=DEVICE_CLASS_POWER, - unit_of_measurement=POWER_WATT, + native_unit_of_measurement=POWER_WATT, state_class=STATE_CLASS_MEASUREMENT, ), SensorEntityDescription( key="ampsIn", name="Amps In", device_class=DEVICE_CLASS_CURRENT, - unit_of_measurement=ELECTRIC_CURRENT_AMPERE, + native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, state_class=STATE_CLASS_MEASUREMENT, entity_registry_enabled_default=False, ), @@ -54,14 +54,14 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="wattsOut", name="Watts Out", device_class=DEVICE_CLASS_POWER, - unit_of_measurement=POWER_WATT, + native_unit_of_measurement=POWER_WATT, state_class=STATE_CLASS_MEASUREMENT, ), SensorEntityDescription( key="ampsOut", name="Amps Out", device_class=DEVICE_CLASS_CURRENT, - unit_of_measurement=ELECTRIC_CURRENT_AMPERE, + native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, state_class=STATE_CLASS_MEASUREMENT, entity_registry_enabled_default=False, ), @@ -69,7 +69,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="whOut", name="WH Out", device_class=DEVICE_CLASS_ENERGY, - unit_of_measurement=ENERGY_WATT_HOUR, + native_unit_of_measurement=ENERGY_WATT_HOUR, state_class=STATE_CLASS_TOTAL_INCREASING, entity_registry_enabled_default=False, ), @@ -77,44 +77,44 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="whStored", name="WH Stored", device_class=DEVICE_CLASS_ENERGY, - unit_of_measurement=ENERGY_WATT_HOUR, + native_unit_of_measurement=ENERGY_WATT_HOUR, state_class=STATE_CLASS_MEASUREMENT, ), SensorEntityDescription( key="volts", name="Volts", device_class=DEVICE_CLASS_VOLTAGE, - unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, entity_registry_enabled_default=False, ), SensorEntityDescription( key="socPercent", name="State of Charge Percent", device_class=DEVICE_CLASS_BATTERY, - unit_of_measurement=PERCENTAGE, + native_unit_of_measurement=PERCENTAGE, ), SensorEntityDescription( key="timeToEmptyFull", name="Time to Empty/Full", device_class=TIME_MINUTES, - unit_of_measurement=TIME_MINUTES, + native_unit_of_measurement=TIME_MINUTES, ), SensorEntityDescription( key="temperature", name="Temperature", device_class=DEVICE_CLASS_TEMPERATURE, - unit_of_measurement=TEMP_CELSIUS, + native_unit_of_measurement=TEMP_CELSIUS, ), SensorEntityDescription( key="wifiStrength", name="Wifi Strength", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, - unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, + native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, ), SensorEntityDescription( key="timestamp", name="Total Run Time", - unit_of_measurement=TIME_SECONDS, + native_unit_of_measurement=TIME_SECONDS, entity_registry_enabled_default=False, ), SensorEntityDescription( diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 7551c971582..8063129d7be 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -5,6 +5,7 @@ from collections.abc import Mapping from contextlib import suppress from dataclasses import dataclass from datetime import datetime, timedelta +import inspect import logging from typing import Any, Final, cast, final @@ -128,9 +129,31 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: class SensorEntityDescription(EntityDescription): """A class that describes sensor entities.""" - state_class: str | None = None last_reset: datetime | None = None # Deprecated, to be removed in 2021.11 native_unit_of_measurement: str | None = None + state_class: str | None = None + unit_of_measurement: None = None # Type override, use native_unit_of_measurement + + def __post_init__(self) -> None: + """Post initialisation processing.""" + if self.unit_of_measurement: + caller = inspect.stack()[2] # type: ignore[unreachable] + module = inspect.getmodule(caller[0]) + if "custom_components" in module.__file__: + report_issue = "report it to the custom component author." + else: + report_issue = ( + "create a bug report at " + "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue" + ) + _LOGGER.warning( + "%s is setting 'unit_of_measurement' on an instance of " + "SensorEntityDescription, this is not valid and will be unsupported " + "from Home Assistant 2021.11. Please %s", + module.__name__, + report_issue, + ) + self.native_unit_of_measurement = self.unit_of_measurement class SensorEntity(Entity): @@ -220,11 +243,6 @@ class SensorEntity(Entity): and self._attr_unit_of_measurement is not None ): return self._attr_unit_of_measurement - if ( - hasattr(self, "entity_description") - and self.entity_description.unit_of_measurement is not None - ): - return self.entity_description.unit_of_measurement native_unit_of_measurement = self.native_unit_of_measurement diff --git a/homeassistant/components/tplink/sensor.py b/homeassistant/components/tplink/sensor.py index b38fa763ee9..4d2ed5eee30 100644 --- a/homeassistant/components/tplink/sensor.py +++ b/homeassistant/components/tplink/sensor.py @@ -53,35 +53,35 @@ ATTR_TOTAL_ENERGY_KWH = "total_energy_kwh" ENERGY_SENSORS: Final[list[SensorEntityDescription]] = [ SensorEntityDescription( key=ATTR_CURRENT_POWER_W, - unit_of_measurement=POWER_WATT, + native_unit_of_measurement=POWER_WATT, device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, name="Current Consumption", ), SensorEntityDescription( key=ATTR_TOTAL_ENERGY_KWH, - unit_of_measurement=ENERGY_KILO_WATT_HOUR, + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, name="Total Consumption", ), SensorEntityDescription( key=ATTR_TODAY_ENERGY_KWH, - unit_of_measurement=ENERGY_KILO_WATT_HOUR, + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, name="Today's Consumption", ), SensorEntityDescription( key=ATTR_VOLTAGE, - unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, + native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, name="Voltage", ), SensorEntityDescription( key=ATTR_CURRENT_A, - unit_of_measurement=ELECTRIC_CURRENT_AMPERE, + native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE, device_class=DEVICE_CLASS_CURRENT, state_class=STATE_CLASS_MEASUREMENT, name="Current", diff --git a/homeassistant/components/wemo/sensor.py b/homeassistant/components/wemo/sensor.py index f1f32e8b909..1fd55e4142e 100644 --- a/homeassistant/components/wemo/sensor.py +++ b/homeassistant/components/wemo/sensor.py @@ -95,7 +95,7 @@ class InsightCurrentPower(InsightSensor): name="Current Power", device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, - unit_of_measurement=POWER_WATT, + native_unit_of_measurement=POWER_WATT, ) @property @@ -115,7 +115,7 @@ class InsightTodayEnergy(InsightSensor): name="Today Energy", device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, - unit_of_measurement=ENERGY_KILO_WATT_HOUR, + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, ) @property diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 5ff2cad9edc..7463cc6755a 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -1,4 +1,5 @@ """The test for sensor device automation.""" +from homeassistant.components.sensor import SensorEntityDescription from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -49,3 +50,12 @@ async def test_deprecated_last_reset(hass, caplog, enable_custom_integrations): "update your configuration if state_class is manually configured, otherwise " "report it to the custom component author." ) in caplog.text + + +async def test_deprecated_unit_of_measurement(hass, caplog, enable_custom_integrations): + """Test warning on deprecated unit_of_measurement.""" + SensorEntityDescription("catsensor", unit_of_measurement="cats") + assert ( + "tests.components.sensor.test_init is setting 'unit_of_measurement' on an " + "instance of SensorEntityDescription" + ) in caplog.text