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
This commit is contained in:
Erik Montnemery 2021-08-20 15:54:57 +02:00 committed by GitHub
parent e134246cbd
commit 2fa07777cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 29 deletions

View file

@ -49,7 +49,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription( SensorEntityDescription(
key="battery_level", key="battery_level",
name="Battery Level", name="Battery Level",
unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -60,19 +60,19 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription( SensorEntityDescription(
key="temperature", key="temperature",
name="Temperature", name="Temperature",
unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
), ),
SensorEntityDescription( SensorEntityDescription(
key="humidity", key="humidity",
name="Humidity", name="Humidity",
unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
), ),
SensorEntityDescription( SensorEntityDescription(
key="air_quality", key="air_quality",
name="Air Quality", name="Air Quality",
unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
icon="mdi:biohazard", icon="mdi:biohazard",
), ),
) )

View file

@ -39,14 +39,14 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key="wattsIn", key="wattsIn",
name="Watts In", name="Watts In",
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_POWER,
unit_of_measurement=POWER_WATT, native_unit_of_measurement=POWER_WATT,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="ampsIn", key="ampsIn",
name="Amps In", name="Amps In",
device_class=DEVICE_CLASS_CURRENT, device_class=DEVICE_CLASS_CURRENT,
unit_of_measurement=ELECTRIC_CURRENT_AMPERE, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
@ -54,14 +54,14 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key="wattsOut", key="wattsOut",
name="Watts Out", name="Watts Out",
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_POWER,
unit_of_measurement=POWER_WATT, native_unit_of_measurement=POWER_WATT,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="ampsOut", key="ampsOut",
name="Amps Out", name="Amps Out",
device_class=DEVICE_CLASS_CURRENT, device_class=DEVICE_CLASS_CURRENT,
unit_of_measurement=ELECTRIC_CURRENT_AMPERE, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
@ -69,7 +69,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key="whOut", key="whOut",
name="WH Out", name="WH Out",
device_class=DEVICE_CLASS_ENERGY, device_class=DEVICE_CLASS_ENERGY,
unit_of_measurement=ENERGY_WATT_HOUR, native_unit_of_measurement=ENERGY_WATT_HOUR,
state_class=STATE_CLASS_TOTAL_INCREASING, state_class=STATE_CLASS_TOTAL_INCREASING,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
@ -77,44 +77,44 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key="whStored", key="whStored",
name="WH Stored", name="WH Stored",
device_class=DEVICE_CLASS_ENERGY, device_class=DEVICE_CLASS_ENERGY,
unit_of_measurement=ENERGY_WATT_HOUR, native_unit_of_measurement=ENERGY_WATT_HOUR,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="volts", key="volts",
name="Volts", name="Volts",
device_class=DEVICE_CLASS_VOLTAGE, device_class=DEVICE_CLASS_VOLTAGE,
unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(
key="socPercent", key="socPercent",
name="State of Charge Percent", name="State of Charge Percent",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
), ),
SensorEntityDescription( SensorEntityDescription(
key="timeToEmptyFull", key="timeToEmptyFull",
name="Time to Empty/Full", name="Time to Empty/Full",
device_class=TIME_MINUTES, device_class=TIME_MINUTES,
unit_of_measurement=TIME_MINUTES, native_unit_of_measurement=TIME_MINUTES,
), ),
SensorEntityDescription( SensorEntityDescription(
key="temperature", key="temperature",
name="Temperature", name="Temperature",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
), ),
SensorEntityDescription( SensorEntityDescription(
key="wifiStrength", key="wifiStrength",
name="Wifi Strength", name="Wifi Strength",
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
), ),
SensorEntityDescription( SensorEntityDescription(
key="timestamp", key="timestamp",
name="Total Run Time", name="Total Run Time",
unit_of_measurement=TIME_SECONDS, native_unit_of_measurement=TIME_SECONDS,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription( SensorEntityDescription(

View file

@ -5,6 +5,7 @@ from collections.abc import Mapping
from contextlib import suppress from contextlib import suppress
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
import inspect
import logging import logging
from typing import Any, Final, cast, final from typing import Any, Final, cast, final
@ -128,9 +129,31 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
class SensorEntityDescription(EntityDescription): class SensorEntityDescription(EntityDescription):
"""A class that describes sensor entities.""" """A class that describes sensor entities."""
state_class: str | None = None
last_reset: datetime | None = None # Deprecated, to be removed in 2021.11 last_reset: datetime | None = None # Deprecated, to be removed in 2021.11
native_unit_of_measurement: str | None = None 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): class SensorEntity(Entity):
@ -220,11 +243,6 @@ class SensorEntity(Entity):
and self._attr_unit_of_measurement is not None and self._attr_unit_of_measurement is not None
): ):
return self._attr_unit_of_measurement 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 native_unit_of_measurement = self.native_unit_of_measurement

View file

@ -53,35 +53,35 @@ ATTR_TOTAL_ENERGY_KWH = "total_energy_kwh"
ENERGY_SENSORS: Final[list[SensorEntityDescription]] = [ ENERGY_SENSORS: Final[list[SensorEntityDescription]] = [
SensorEntityDescription( SensorEntityDescription(
key=ATTR_CURRENT_POWER_W, key=ATTR_CURRENT_POWER_W,
unit_of_measurement=POWER_WATT, native_unit_of_measurement=POWER_WATT,
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
name="Current Consumption", name="Current Consumption",
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_TOTAL_ENERGY_KWH, 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, device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING, state_class=STATE_CLASS_TOTAL_INCREASING,
name="Total Consumption", name="Total Consumption",
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_TODAY_ENERGY_KWH, 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, device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING, state_class=STATE_CLASS_TOTAL_INCREASING,
name="Today's Consumption", name="Today's Consumption",
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_VOLTAGE, key=ATTR_VOLTAGE,
unit_of_measurement=ELECTRIC_POTENTIAL_VOLT, native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
device_class=DEVICE_CLASS_VOLTAGE, device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
name="Voltage", name="Voltage",
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_CURRENT_A, key=ATTR_CURRENT_A,
unit_of_measurement=ELECTRIC_CURRENT_AMPERE, native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
device_class=DEVICE_CLASS_CURRENT, device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
name="Current", name="Current",

View file

@ -95,7 +95,7 @@ class InsightCurrentPower(InsightSensor):
name="Current Power", name="Current Power",
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=POWER_WATT, native_unit_of_measurement=POWER_WATT,
) )
@property @property
@ -115,7 +115,7 @@ class InsightTodayEnergy(InsightSensor):
name="Today Energy", name="Today Energy",
device_class=DEVICE_CLASS_ENERGY, device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING, state_class=STATE_CLASS_TOTAL_INCREASING,
unit_of_measurement=ENERGY_KILO_WATT_HOUR, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
) )
@property @property

View file

@ -1,4 +1,5 @@
"""The test for sensor device automation.""" """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.const import ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util 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 " "update your configuration if state_class is manually configured, otherwise "
"report it to the custom component author." "report it to the custom component author."
) in caplog.text ) 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