Refactor support for integrations to drop custom unit conversion (#83228)

* Refactor support for integrations to drop custom unit conversion

* Fix lying comment

* Address review comment
This commit is contained in:
Erik Montnemery 2022-12-05 16:12:37 +01:00 committed by GitHub
parent 1f7a7d5cb5
commit 67875b99a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 28 deletions

View File

@ -484,10 +484,19 @@ class SensorEntity(Entity):
platform: EntityPlatform, platform: EntityPlatform,
parallel_updates: asyncio.Semaphore | None, parallel_updates: asyncio.Semaphore | None,
) -> None: ) -> None:
"""Start adding an entity to a platform.""" """Start adding an entity to a platform.
Allows integrations to remove legacy custom unit conversion which is no longer
needed without breaking existing sensors. Only works for sensors which are in
the entity registry.
This can be removed once core integrations have dropped unneeded custom unit
conversion.
"""
super().add_to_platform_start(hass, platform, parallel_updates) super().add_to_platform_start(hass, platform, parallel_updates)
if self.unique_id is None: # Bail out if the sensor doesn't have a unique_id or a device class
if self.unique_id is None or self.device_class is None:
return return
registry = er.async_get(self.hass) registry = er.async_get(self.hass)
if not ( if not (
@ -499,23 +508,35 @@ class SensorEntity(Entity):
registry_entry = registry.async_get(entity_id) registry_entry = registry.async_get(entity_id)
assert registry_entry assert registry_entry
# Store unit override according to automatic unit conversion rules if: # If the sensor has 'unit_of_measurement' in its sensor options, the user has
# - no unit override is stored in the entity registry # overridden the unit.
# - units have changed # If the sensor has 'sensor.private' in its entity options, it was added after
# - the unit stored in the registry matches automatic unit conversion rules # automatic unit conversion was implemented.
# This allows integrations to drop custom unit conversion and rely on automatic
# conversion.
registry_unit = registry_entry.unit_of_measurement registry_unit = registry_entry.unit_of_measurement
if ( if (
DOMAIN not in registry_entry.options (
and f"{DOMAIN}.private" not in registry_entry.options (sensor_options := registry_entry.options.get(DOMAIN))
and self.unit_of_measurement != registry_unit and CONF_UNIT_OF_MEASUREMENT in sensor_options
and (suggested_unit := self._get_initial_suggested_unit()) == registry_unit )
or f"{DOMAIN}.private" in registry_entry.options
or self.unit_of_measurement == registry_unit
): ):
return
# Make sure we can convert the units
if (
(unit_converter := UNIT_CONVERTERS.get(self.device_class)) is None
or registry_unit not in unit_converter.VALID_UNITS
or self.unit_of_measurement not in unit_converter.VALID_UNITS
):
return
# Set suggested_unit_of_measurement to the old unit to enable automatic
# conversion
registry.async_update_entity_options( registry.async_update_entity_options(
entity_id, entity_id,
f"{DOMAIN}.private", f"{DOMAIN}.private",
{"suggested_unit_of_measurement": suggested_unit}, {"suggested_unit_of_measurement": registry_unit},
) )
async def async_internal_added_to_hass(self) -> None: async def async_internal_added_to_hass(self) -> None:
@ -572,8 +593,12 @@ class SensorEntity(Entity):
return None return None
def _get_initial_suggested_unit(self) -> str | None: def get_initial_entity_options(self) -> er.EntityOptionsType | None:
"""Return initial suggested unit of measurement.""" """Return initial entity options.
These will be stored in the entity registry the first time the entity is seen,
and then never updated.
"""
# Unit suggested by the integration # Unit suggested by the integration
suggested_unit_of_measurement = self.suggested_unit_of_measurement suggested_unit_of_measurement = self.suggested_unit_of_measurement
@ -583,15 +608,6 @@ class SensorEntity(Entity):
self.device_class, self.native_unit_of_measurement self.device_class, self.native_unit_of_measurement
) )
return suggested_unit_of_measurement
def get_initial_entity_options(self) -> er.EntityOptionsType | None:
"""Return initial entity options.
These will be stored in the entity registry the first time the entity is seen,
and then never updated.
"""
suggested_unit_of_measurement = self._get_initial_suggested_unit()
if suggested_unit_of_measurement is None: if suggested_unit_of_measurement is None:
return None return None

View File

@ -889,6 +889,14 @@ async def test_unit_conversion_priority_suggested_unit_change(
621, 621,
SensorDeviceClass.DISTANCE, SensorDeviceClass.DISTANCE,
), ),
(
US_CUSTOMARY_SYSTEM,
LENGTH_METERS,
LENGTH_MILES,
1000000,
621.371,
SensorDeviceClass.DISTANCE,
),
], ],
) )
async def test_unit_conversion_priority_legacy_conversion_removed( async def test_unit_conversion_priority_legacy_conversion_removed(