Align temperature conversion with other converters (#79521)

* Align temperature conversion with other converters

* Add comments and docstring

* Align tests
This commit is contained in:
epenet 2022-10-03 10:09:55 +02:00 committed by GitHub
parent 47b40e1e61
commit 825f9502ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 63 deletions

View file

@ -819,7 +819,9 @@ def temperature_from_object(hass, temp_obj, interval=False):
# convert to Celsius if absolute temperature # convert to Celsius if absolute temperature
temp -= 273.15 temp -= 273.15
return TemperatureConverter.convert(temp, from_unit, to_unit, interval=interval) if interval:
return TemperatureConverter.convert_interval(temp, from_unit, to_unit)
return TemperatureConverter.convert(temp, from_unit, to_unit)
@HANDLERS.register(("Alexa.ThermostatController", "SetTargetTemperature")) @HANDLERS.register(("Alexa.ThermostatController", "SetTargetTemperature"))

View file

@ -43,6 +43,6 @@ def convert(
"unit_conversion.TemperatureConverter instead", "unit_conversion.TemperatureConverter instead",
error_if_core=False, error_if_core=False,
) )
return TemperatureConverter.convert( if interval:
temperature, from_unit, to_unit, interval=interval return TemperatureConverter.convert_interval(temperature, from_unit, to_unit)
) return TemperatureConverter.convert(temperature, from_unit, to_unit)

View file

@ -1,8 +1,6 @@
"""Typing Helpers for Home Assistant.""" """Typing Helpers for Home Assistant."""
from __future__ import annotations from __future__ import annotations
from abc import abstractmethod
from homeassistant.const import ( from homeassistant.const import (
ENERGY_KILO_WATT_HOUR, ENERGY_KILO_WATT_HOUR,
ENERGY_MEGA_WATT_HOUR, ENERGY_MEGA_WATT_HOUR,
@ -88,20 +86,6 @@ class BaseUnitConverter:
NORMALIZED_UNIT: str NORMALIZED_UNIT: str
VALID_UNITS: set[str] VALID_UNITS: set[str]
@classmethod
@abstractmethod
def convert(cls, value: float, from_unit: str, to_unit: str) -> float:
"""Convert one unit of measurement to another."""
@classmethod
@abstractmethod
def get_unit_ratio(cls, from_unit: str, to_unit: str) -> float:
"""Get unit ratio between units of measurement."""
class BaseUnitConverterWithUnitConversion(BaseUnitConverter):
"""Define the format of a conversion utility."""
_UNIT_CONVERSION: dict[str, float] _UNIT_CONVERSION: dict[str, float]
@classmethod @classmethod
@ -133,7 +117,7 @@ class BaseUnitConverterWithUnitConversion(BaseUnitConverter):
return cls._UNIT_CONVERSION[from_unit] / cls._UNIT_CONVERSION[to_unit] return cls._UNIT_CONVERSION[from_unit] / cls._UNIT_CONVERSION[to_unit]
class DistanceConverter(BaseUnitConverterWithUnitConversion): class DistanceConverter(BaseUnitConverter):
"""Utility to convert distance values.""" """Utility to convert distance values."""
UNIT_CLASS = "distance" UNIT_CLASS = "distance"
@ -160,7 +144,7 @@ class DistanceConverter(BaseUnitConverterWithUnitConversion):
} }
class EnergyConverter(BaseUnitConverterWithUnitConversion): class EnergyConverter(BaseUnitConverter):
"""Utility to convert energy values.""" """Utility to convert energy values."""
UNIT_CLASS = "energy" UNIT_CLASS = "energy"
@ -177,7 +161,7 @@ class EnergyConverter(BaseUnitConverterWithUnitConversion):
} }
class MassConverter(BaseUnitConverterWithUnitConversion): class MassConverter(BaseUnitConverter):
"""Utility to convert mass values.""" """Utility to convert mass values."""
UNIT_CLASS = "mass" UNIT_CLASS = "mass"
@ -200,7 +184,7 @@ class MassConverter(BaseUnitConverterWithUnitConversion):
} }
class PowerConverter(BaseUnitConverterWithUnitConversion): class PowerConverter(BaseUnitConverter):
"""Utility to convert power values.""" """Utility to convert power values."""
UNIT_CLASS = "power" UNIT_CLASS = "power"
@ -215,7 +199,7 @@ class PowerConverter(BaseUnitConverterWithUnitConversion):
} }
class PressureConverter(BaseUnitConverterWithUnitConversion): class PressureConverter(BaseUnitConverter):
"""Utility to convert pressure values.""" """Utility to convert pressure values."""
UNIT_CLASS = "pressure" UNIT_CLASS = "pressure"
@ -244,7 +228,7 @@ class PressureConverter(BaseUnitConverterWithUnitConversion):
} }
class SpeedConverter(BaseUnitConverterWithUnitConversion): class SpeedConverter(BaseUnitConverter):
"""Utility to convert speed values.""" """Utility to convert speed values."""
UNIT_CLASS = "speed" UNIT_CLASS = "speed"
@ -281,47 +265,49 @@ class TemperatureConverter(BaseUnitConverter):
TEMP_FAHRENHEIT, TEMP_FAHRENHEIT,
TEMP_KELVIN, TEMP_KELVIN,
} }
_UNIT_RATIO = { _UNIT_CONVERSION = {
TEMP_CELSIUS: 1.0, TEMP_CELSIUS: 1.0,
TEMP_FAHRENHEIT: 1.8, TEMP_FAHRENHEIT: 1.8,
TEMP_KELVIN: 1.0, TEMP_KELVIN: 1.0,
} }
@classmethod @classmethod
def convert( def convert(cls, value: float, from_unit: str, to_unit: str) -> float:
cls, value: float, from_unit: str, to_unit: str, *, interval: bool = False """Convert a temperature from one unit to another.
) -> float:
"""Convert a temperature from one unit to another.""" eg. 10°C will return 50°F
For converting an interval between two temperatures, please use
`convert_interval` instead.
"""
# We cannot use the implementation from BaseUnitConverter here because the temperature
# units do not use the same floor: 0°C, 0°F and 0K do not align
if from_unit == to_unit: if from_unit == to_unit:
return value return value
if from_unit == TEMP_CELSIUS: if from_unit == TEMP_CELSIUS:
if to_unit == TEMP_FAHRENHEIT: if to_unit == TEMP_FAHRENHEIT:
return cls._celsius_to_fahrenheit(value, interval) return cls._celsius_to_fahrenheit(value)
if to_unit == TEMP_KELVIN: if to_unit == TEMP_KELVIN:
return cls._celsius_to_kelvin(value, interval) return cls._celsius_to_kelvin(value)
raise HomeAssistantError( raise HomeAssistantError(
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS) UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
) )
if from_unit == TEMP_FAHRENHEIT: if from_unit == TEMP_FAHRENHEIT:
if to_unit == TEMP_CELSIUS: if to_unit == TEMP_CELSIUS:
return cls._fahrenheit_to_celsius(value, interval) return cls._fahrenheit_to_celsius(value)
if to_unit == TEMP_KELVIN: if to_unit == TEMP_KELVIN:
return cls._celsius_to_kelvin( return cls._celsius_to_kelvin(cls._fahrenheit_to_celsius(value))
cls._fahrenheit_to_celsius(value, interval), interval
)
raise HomeAssistantError( raise HomeAssistantError(
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS) UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
) )
if from_unit == TEMP_KELVIN: if from_unit == TEMP_KELVIN:
if to_unit == TEMP_CELSIUS: if to_unit == TEMP_CELSIUS:
return cls._kelvin_to_celsius(value, interval) return cls._kelvin_to_celsius(value)
if to_unit == TEMP_FAHRENHEIT: if to_unit == TEMP_FAHRENHEIT:
return cls._celsius_to_fahrenheit( return cls._celsius_to_fahrenheit(cls._kelvin_to_celsius(value))
cls._kelvin_to_celsius(value, interval), interval
)
raise HomeAssistantError( raise HomeAssistantError(
UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS) UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS)
) )
@ -330,40 +316,40 @@ class TemperatureConverter(BaseUnitConverter):
) )
@classmethod @classmethod
def _fahrenheit_to_celsius(cls, fahrenheit: float, interval: bool = False) -> float: def convert_interval(cls, interval: float, from_unit: str, to_unit: str) -> float:
"""Convert a temperature interval from one unit to another.
eg. a 10°C interval (10°C to 20°C) will return a 18°F (50°F to 68°F) interval
For converting a temperature value, please use `convert` as this method
skips floor adjustment.
"""
# We use BaseUnitConverter implementation here because we are only interested
# in the ratio between the units.
return super().convert(interval, from_unit, to_unit)
@classmethod
def _fahrenheit_to_celsius(cls, fahrenheit: float) -> float:
"""Convert a temperature in Fahrenheit to Celsius.""" """Convert a temperature in Fahrenheit to Celsius."""
if interval:
return fahrenheit / 1.8
return (fahrenheit - 32.0) / 1.8 return (fahrenheit - 32.0) / 1.8
@classmethod @classmethod
def _kelvin_to_celsius(cls, kelvin: float, interval: bool = False) -> float: def _kelvin_to_celsius(cls, kelvin: float) -> float:
"""Convert a temperature in Kelvin to Celsius.""" """Convert a temperature in Kelvin to Celsius."""
if interval:
return kelvin
return kelvin - 273.15 return kelvin - 273.15
@classmethod @classmethod
def _celsius_to_fahrenheit(cls, celsius: float, interval: bool = False) -> float: def _celsius_to_fahrenheit(cls, celsius: float) -> float:
"""Convert a temperature in Celsius to Fahrenheit.""" """Convert a temperature in Celsius to Fahrenheit."""
if interval:
return celsius * 1.8
return celsius * 1.8 + 32.0 return celsius * 1.8 + 32.0
@classmethod @classmethod
def _celsius_to_kelvin(cls, celsius: float, interval: bool = False) -> float: def _celsius_to_kelvin(cls, celsius: float) -> float:
"""Convert a temperature in Celsius to Kelvin.""" """Convert a temperature in Celsius to Kelvin."""
if interval:
return celsius
return celsius + 273.15 return celsius + 273.15
@classmethod
def get_unit_ratio(cls, from_unit: str, to_unit: str) -> float:
"""Get unit ratio between units of measurement."""
return cls._UNIT_RATIO[from_unit] / cls._UNIT_RATIO[to_unit]
class VolumeConverter(BaseUnitConverter):
class VolumeConverter(BaseUnitConverterWithUnitConversion):
"""Utility to convert volume values.""" """Utility to convert volume values."""
UNIT_CLASS = "volume" UNIT_CLASS = "volume"

View file

@ -452,10 +452,7 @@ def test_temperature_convert_with_interval(
value: float, from_unit: str, expected: float, to_unit: str value: float, from_unit: str, expected: float, to_unit: str
) -> None: ) -> None:
"""Test conversion to other units.""" """Test conversion to other units."""
assert ( assert TemperatureConverter.convert_interval(value, from_unit, to_unit) == expected
TemperatureConverter.convert(value, from_unit, to_unit, interval=True)
== expected
)
@pytest.mark.parametrize( @pytest.mark.parametrize(