diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index b7362d1a381..f2ae6ee5e70 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -30,11 +30,7 @@ from homeassistant.helpers import entity_registry from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.helpers.typing import UNDEFINED, UndefinedType -from homeassistant.util import ( - dt as dt_util, - temperature as temperature_util, - volume as volume_util, -) +from homeassistant.util import dt as dt_util, temperature as temperature_util from homeassistant.util.unit_conversion import ( BaseUnitConverter, EnergyConverter, @@ -168,12 +164,12 @@ def _convert_volume_from_m3(to_unit: str, value: float | None) -> float | None: """Convert volume in m³ to to_unit.""" if value is None: return None - return volume_util.convert(value, volume_util.NORMALIZED_UNIT, to_unit) + return VolumeConverter.convert(value, VolumeConverter.NORMALIZED_UNIT, to_unit) def _convert_volume_to_m3(from_unit: str, value: float) -> float: """Convert volume in from_unit to m³.""" - return volume_util.convert(value, from_unit, volume_util.NORMALIZED_UNIT) + return VolumeConverter.convert(value, from_unit, VolumeConverter.NORMALIZED_UNIT) STATISTIC_UNIT_TO_UNIT_CLASS: dict[str | None, str] = { @@ -181,7 +177,7 @@ STATISTIC_UNIT_TO_UNIT_CLASS: dict[str | None, str] = { PowerConverter.NORMALIZED_UNIT: PowerConverter.UNIT_CLASS, PressureConverter.NORMALIZED_UNIT: PressureConverter.UNIT_CLASS, temperature_util.NORMALIZED_UNIT: "temperature", - volume_util.NORMALIZED_UNIT: "volume", + VolumeConverter.NORMALIZED_UNIT: "volume", } STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = { @@ -189,7 +185,7 @@ STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = { PowerConverter.NORMALIZED_UNIT: PowerConverter, PressureConverter.NORMALIZED_UNIT: PressureConverter, temperature_util.NORMALIZED_UNIT: TemperatureConverter, - volume_util.NORMALIZED_UNIT: VolumeConverter, + VolumeConverter.NORMALIZED_UNIT: VolumeConverter, } # Convert energy power, pressure, temperature and volume statistics from the @@ -201,7 +197,7 @@ STATISTIC_UNIT_TO_DISPLAY_UNIT_FUNCTIONS: dict[ PowerConverter.NORMALIZED_UNIT: _convert_power_from_w, PressureConverter.NORMALIZED_UNIT: _convert_pressure_from_pa, temperature_util.NORMALIZED_UNIT: _convert_temperature_from_c, - volume_util.NORMALIZED_UNIT: _convert_volume_from_m3, + VolumeConverter.NORMALIZED_UNIT: _convert_volume_from_m3, } # Convert energy and volume statistics from the display unit configured by the user @@ -209,7 +205,7 @@ STATISTIC_UNIT_TO_DISPLAY_UNIT_FUNCTIONS: dict[ # This is used to support adjusting statistics in the display unit DISPLAY_UNIT_TO_STATISTIC_UNIT_FUNCTIONS: dict[str, Callable[[str, float], float]] = { EnergyConverter.NORMALIZED_UNIT: _convert_energy_to_kwh, - volume_util.NORMALIZED_UNIT: _convert_volume_to_m3, + VolumeConverter.NORMALIZED_UNIT: _convert_volume_to_m3, } _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index a66ef54304a..0f028214b25 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -47,11 +47,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, State from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity import entity_sources -from homeassistant.util import ( - dt as dt_util, - temperature as temperature_util, - volume as volume_util, -) +from homeassistant.util import dt as dt_util, temperature as temperature_util from homeassistant.util.unit_conversion import ( BaseUnitConverter, EnergyConverter, @@ -124,7 +120,8 @@ UNIT_CONVERSIONS: dict[str, dict[str, Callable]] = { # Convert volume to cubic meter SensorDeviceClass.GAS: { VOLUME_CUBIC_METERS: lambda x: x, - VOLUME_CUBIC_FEET: volume_util.cubic_feet_to_cubic_meter, + VOLUME_CUBIC_FEET: lambda x: x + / VolumeConverter.UNIT_CONVERSION[VOLUME_CUBIC_FEET], }, } diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index 3d82a76e2d1..b2b4a8fda63 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -21,10 +21,23 @@ from homeassistant.const import ( PRESSURE_PSI, TEMP_CELSIUS, UNIT_NOT_RECOGNIZED_TEMPLATE, + VOLUME_CUBIC_FEET, VOLUME_CUBIC_METERS, + VOLUME_FLUID_OUNCE, + VOLUME_GALLONS, + VOLUME_LITERS, + VOLUME_MILLILITERS, ) -from . import temperature as temperature_util, volume as volume_util +from . import temperature as temperature_util +from .distance import FOOT_TO_M, IN_TO_M + +# Volume conversion constants +_L_TO_CUBIC_METER = 0.001 # 1 L = 0.001 m³ +_ML_TO_CUBIC_METER = 0.001 * _L_TO_CUBIC_METER # 1 mL = 0.001 L +_GALLON_TO_CUBIC_METER = 231 * pow(IN_TO_M, 3) # US gallon is 231 cubic inches +_FLUID_OUNCE_TO_CUBIC_METER = _GALLON_TO_CUBIC_METER / 128 # 128 fl. oz. in a US gallon +_CUBIC_FOOT_TO_CUBIC_METER = pow(FOOT_TO_M, 3) class BaseUnitConverter: @@ -132,9 +145,25 @@ class TemperatureConverter(BaseUnitConverter): convert = temperature_util.convert -class VolumeConverter(BaseUnitConverter): +class VolumeConverter(BaseUnitConverterWithUnitConversion): """Utility to convert volume values.""" + UNIT_CLASS = "volume" NORMALIZED_UNIT = VOLUME_CUBIC_METERS - VALID_UNITS = volume_util.VALID_UNITS - convert = volume_util.convert + # Units in terms of m³ + UNIT_CONVERSION: dict[str, float] = { + VOLUME_LITERS: 1 / _L_TO_CUBIC_METER, + VOLUME_MILLILITERS: 1 / _ML_TO_CUBIC_METER, + VOLUME_GALLONS: 1 / _GALLON_TO_CUBIC_METER, + VOLUME_FLUID_OUNCE: 1 / _FLUID_OUNCE_TO_CUBIC_METER, + VOLUME_CUBIC_METERS: 1, + VOLUME_CUBIC_FEET: 1 / _CUBIC_FOOT_TO_CUBIC_METER, + } + VALID_UNITS: tuple[str, ...] = ( + VOLUME_LITERS, + VOLUME_MILLILITERS, + VOLUME_GALLONS, + VOLUME_FLUID_OUNCE, + VOLUME_CUBIC_METERS, + VOLUME_CUBIC_FEET, + ) diff --git a/homeassistant/util/volume.py b/homeassistant/util/volume.py index 5cc6dbc58dd..1e1ea20fbff 100644 --- a/homeassistant/util/volume.py +++ b/homeassistant/util/volume.py @@ -1,9 +1,7 @@ """Volume conversion util functions.""" from __future__ import annotations -from numbers import Number - -from homeassistant.const import ( +from homeassistant.const import ( # pylint: disable=unused-import # noqa: F401 UNIT_NOT_RECOGNIZED_TEMPLATE, VOLUME, VOLUME_CUBIC_FEET, @@ -14,69 +12,40 @@ from homeassistant.const import ( VOLUME_MILLILITERS, ) -from .distance import FOOT_TO_M, IN_TO_M +from .unit_conversion import VolumeConverter -VALID_UNITS: tuple[str, ...] = ( - VOLUME_LITERS, - VOLUME_MILLILITERS, - VOLUME_GALLONS, - VOLUME_FLUID_OUNCE, - VOLUME_CUBIC_METERS, - VOLUME_CUBIC_FEET, -) - -L_TO_CUBIC_METER = 0.001 # 1 L = 0.001 m³ -ML_TO_CUBIC_METER = 0.001 * L_TO_CUBIC_METER # 1 mL = 0.001 L -GALLON_TO_CUBIC_METER = 231 * pow(IN_TO_M, 3) # US gallon is 231 cubic inches -FLUID_OUNCE_TO_CUBIC_METER = GALLON_TO_CUBIC_METER / 128 # 128 fl. oz. in a US gallon -CUBIC_FOOT_TO_CUBIC_METER = pow(FOOT_TO_M, 3) - -# Units in terms of m³ -UNIT_CONVERSION: dict[str, float] = { - VOLUME_LITERS: 1 / L_TO_CUBIC_METER, - VOLUME_MILLILITERS: 1 / ML_TO_CUBIC_METER, - VOLUME_GALLONS: 1 / GALLON_TO_CUBIC_METER, - VOLUME_FLUID_OUNCE: 1 / FLUID_OUNCE_TO_CUBIC_METER, - VOLUME_CUBIC_METERS: 1, - VOLUME_CUBIC_FEET: 1 / CUBIC_FOOT_TO_CUBIC_METER, -} - -NORMALIZED_UNIT = VOLUME_CUBIC_METERS +UNIT_CONVERSION = VolumeConverter.UNIT_CONVERSION +VALID_UNITS = VolumeConverter.VALID_UNITS def liter_to_gallon(liter: float) -> float: """Convert a volume measurement in Liter to Gallon.""" + # Need to add warning when core migration finished return _convert(liter, VOLUME_LITERS, VOLUME_GALLONS) def gallon_to_liter(gallon: float) -> float: """Convert a volume measurement in Gallon to Liter.""" + # Need to add warning when core migration finished return _convert(gallon, VOLUME_GALLONS, VOLUME_LITERS) def cubic_meter_to_cubic_feet(cubic_meter: float) -> float: """Convert a volume measurement in cubic meter to cubic feet.""" + # Need to add warning when core migration finished return _convert(cubic_meter, VOLUME_CUBIC_METERS, VOLUME_CUBIC_FEET) def cubic_feet_to_cubic_meter(cubic_feet: float) -> float: """Convert a volume measurement in cubic feet to cubic meter.""" + # Need to add warning when core migration finished return _convert(cubic_feet, VOLUME_CUBIC_FEET, VOLUME_CUBIC_METERS) def convert(volume: float, from_unit: str, to_unit: str) -> float: """Convert a volume from one unit to another.""" - if from_unit not in VALID_UNITS: - raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(from_unit, VOLUME)) - if to_unit not in VALID_UNITS: - raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, VOLUME)) - - if not isinstance(volume, Number): - raise TypeError(f"{volume} is not of numeric type") - - if from_unit == to_unit: - return volume - return _convert(volume, from_unit, to_unit) + # Need to add warning when core migration finished + return VolumeConverter.convert(volume, from_unit, to_unit) def _convert(volume: float, from_unit: str, to_unit: str) -> float: diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index f3dfd033be7..b4183375926 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -15,12 +15,19 @@ from homeassistant.const import ( PRESSURE_MMHG, PRESSURE_PA, PRESSURE_PSI, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, + VOLUME_FLUID_OUNCE, + VOLUME_GALLONS, + VOLUME_LITERS, + VOLUME_MILLILITERS, ) from homeassistant.util.unit_conversion import ( BaseUnitConverter, EnergyConverter, PowerConverter, PressureConverter, + VolumeConverter, ) INVALID_SYMBOL = "bob" @@ -42,6 +49,10 @@ INVALID_SYMBOL = "bob" (PressureConverter, PRESSURE_CBAR), (PressureConverter, PRESSURE_MMHG), (PressureConverter, PRESSURE_PSI), + (VolumeConverter, VOLUME_LITERS), + (VolumeConverter, VOLUME_MILLILITERS), + (VolumeConverter, VOLUME_GALLONS), + (VolumeConverter, VOLUME_FLUID_OUNCE), ], ) def test_convert_same_unit(converter: type[BaseUnitConverter], valid_unit: str) -> None: @@ -55,6 +66,7 @@ def test_convert_same_unit(converter: type[BaseUnitConverter], valid_unit: str) (EnergyConverter, ENERGY_KILO_WATT_HOUR), (PowerConverter, POWER_WATT), (PressureConverter, PRESSURE_PA), + (VolumeConverter, VOLUME_LITERS), ], ) def test_convert_invalid_unit( @@ -74,6 +86,7 @@ def test_convert_invalid_unit( (EnergyConverter, ENERGY_WATT_HOUR, ENERGY_KILO_WATT_HOUR), (PowerConverter, POWER_WATT, POWER_KILO_WATT), (PressureConverter, PRESSURE_HPA, PRESSURE_INHG), + (VolumeConverter, VOLUME_GALLONS, VOLUME_LITERS), ], ) def test_convert_nonnumeric_value( @@ -85,206 +98,128 @@ def test_convert_nonnumeric_value( @pytest.mark.parametrize( - "converter,value,from_unit,expected,to_unit", + "value,from_unit,expected,to_unit", [ - (EnergyConverter, 10, ENERGY_WATT_HOUR, 0.01, ENERGY_KILO_WATT_HOUR), - (EnergyConverter, 10, ENERGY_WATT_HOUR, 0.00001, ENERGY_MEGA_WATT_HOUR), - (EnergyConverter, 10, ENERGY_KILO_WATT_HOUR, 10000, ENERGY_WATT_HOUR), - (EnergyConverter, 10, ENERGY_KILO_WATT_HOUR, 0.01, ENERGY_MEGA_WATT_HOUR), - (EnergyConverter, 10, ENERGY_MEGA_WATT_HOUR, 10000000, ENERGY_WATT_HOUR), - (EnergyConverter, 10, ENERGY_MEGA_WATT_HOUR, 10000, ENERGY_KILO_WATT_HOUR), - (PowerConverter, 10, POWER_KILO_WATT, 10000, POWER_WATT), - (PowerConverter, 10, POWER_WATT, 0.01, POWER_KILO_WATT), - ( - PressureConverter, - 1000, - PRESSURE_HPA, - pytest.approx(14.5037743897), - PRESSURE_PSI, - ), - ( - PressureConverter, - 1000, - PRESSURE_HPA, - pytest.approx(29.5299801647), - PRESSURE_INHG, - ), - ( - PressureConverter, - 1000, - PRESSURE_HPA, - pytest.approx(100000), - PRESSURE_PA, - ), - ( - PressureConverter, - 1000, - PRESSURE_HPA, - pytest.approx(100), - PRESSURE_KPA, - ), - ( - PressureConverter, - 1000, - PRESSURE_HPA, - pytest.approx(1000), - PRESSURE_MBAR, - ), - ( - PressureConverter, - 1000, - PRESSURE_HPA, - pytest.approx(100), - PRESSURE_CBAR, - ), - ( - PressureConverter, - 100, - PRESSURE_KPA, - pytest.approx(14.5037743897), - PRESSURE_PSI, - ), - ( - PressureConverter, - 100, - PRESSURE_KPA, - pytest.approx(29.5299801647), - PRESSURE_INHG, - ), - ( - PressureConverter, - 100, - PRESSURE_KPA, - pytest.approx(100000), - PRESSURE_PA, - ), - ( - PressureConverter, - 100, - PRESSURE_KPA, - pytest.approx(1000), - PRESSURE_HPA, - ), - ( - PressureConverter, - 100, - PRESSURE_KPA, - pytest.approx(1000), - PRESSURE_MBAR, - ), - ( - PressureConverter, - 100, - PRESSURE_KPA, - pytest.approx(100), - PRESSURE_CBAR, - ), - ( - PressureConverter, - 30, - PRESSURE_INHG, - pytest.approx(14.7346266155), - PRESSURE_PSI, - ), - ( - PressureConverter, - 30, - PRESSURE_INHG, - pytest.approx(101.59167), - PRESSURE_KPA, - ), - ( - PressureConverter, - 30, - PRESSURE_INHG, - pytest.approx(1015.9167), - PRESSURE_HPA, - ), - ( - PressureConverter, - 30, - PRESSURE_INHG, - pytest.approx(101591.67), - PRESSURE_PA, - ), - ( - PressureConverter, - 30, - PRESSURE_INHG, - pytest.approx(1015.9167), - PRESSURE_MBAR, - ), - ( - PressureConverter, - 30, - PRESSURE_INHG, - pytest.approx(101.59167), - PRESSURE_CBAR, - ), - ( - PressureConverter, - 30, - PRESSURE_INHG, - pytest.approx(762.002), - PRESSURE_MMHG, - ), - ( - PressureConverter, - 30, - PRESSURE_MMHG, - pytest.approx(0.580102), - PRESSURE_PSI, - ), - ( - PressureConverter, - 30, - PRESSURE_MMHG, - pytest.approx(3.99966), - PRESSURE_KPA, - ), - ( - PressureConverter, - 30, - PRESSURE_MMHG, - pytest.approx(39.9966), - PRESSURE_HPA, - ), - ( - PressureConverter, - 30, - PRESSURE_MMHG, - pytest.approx(3999.66), - PRESSURE_PA, - ), - ( - PressureConverter, - 30, - PRESSURE_MMHG, - pytest.approx(39.9966), - PRESSURE_MBAR, - ), - ( - PressureConverter, - 30, - PRESSURE_MMHG, - pytest.approx(3.99966), - PRESSURE_CBAR, - ), - ( - PressureConverter, - 30, - PRESSURE_MMHG, - pytest.approx(1.181099), - PRESSURE_INHG, - ), + (10, ENERGY_WATT_HOUR, 0.01, ENERGY_KILO_WATT_HOUR), + (10, ENERGY_WATT_HOUR, 0.00001, ENERGY_MEGA_WATT_HOUR), + (10, ENERGY_KILO_WATT_HOUR, 10000, ENERGY_WATT_HOUR), + (10, ENERGY_KILO_WATT_HOUR, 0.01, ENERGY_MEGA_WATT_HOUR), + (10, ENERGY_MEGA_WATT_HOUR, 10000000, ENERGY_WATT_HOUR), + (10, ENERGY_MEGA_WATT_HOUR, 10000, ENERGY_KILO_WATT_HOUR), ], ) -def test_convert( - converter: type[BaseUnitConverter], +def test_energy_convert( value: float, from_unit: str, expected: float, to_unit: str, ) -> None: """Test conversion to other units.""" - assert converter.convert(value, from_unit, to_unit) == expected + assert EnergyConverter.convert(value, from_unit, to_unit) == expected + + +@pytest.mark.parametrize( + "value,from_unit,expected,to_unit", + [ + (10, POWER_KILO_WATT, 10000, POWER_WATT), + (10, POWER_WATT, 0.01, POWER_KILO_WATT), + ], +) +def test_power_convert( + value: float, + from_unit: str, + expected: float, + to_unit: str, +) -> None: + """Test conversion to other units.""" + assert PowerConverter.convert(value, from_unit, to_unit) == expected + + +@pytest.mark.parametrize( + "value,from_unit,expected,to_unit", + [ + (1000, PRESSURE_HPA, pytest.approx(14.5037743897), PRESSURE_PSI), + (1000, PRESSURE_HPA, pytest.approx(29.5299801647), PRESSURE_INHG), + (1000, PRESSURE_HPA, pytest.approx(100000), PRESSURE_PA), + (1000, PRESSURE_HPA, pytest.approx(100), PRESSURE_KPA), + (1000, PRESSURE_HPA, pytest.approx(1000), PRESSURE_MBAR), + (1000, PRESSURE_HPA, pytest.approx(100), PRESSURE_CBAR), + (100, PRESSURE_KPA, pytest.approx(14.5037743897), PRESSURE_PSI), + (100, PRESSURE_KPA, pytest.approx(29.5299801647), PRESSURE_INHG), + (100, PRESSURE_KPA, pytest.approx(100000), PRESSURE_PA), + (100, PRESSURE_KPA, pytest.approx(1000), PRESSURE_HPA), + (100, PRESSURE_KPA, pytest.approx(1000), PRESSURE_MBAR), + (100, PRESSURE_KPA, pytest.approx(100), PRESSURE_CBAR), + (30, PRESSURE_INHG, pytest.approx(14.7346266155), PRESSURE_PSI), + (30, PRESSURE_INHG, pytest.approx(101.59167), PRESSURE_KPA), + (30, PRESSURE_INHG, pytest.approx(1015.9167), PRESSURE_HPA), + (30, PRESSURE_INHG, pytest.approx(101591.67), PRESSURE_PA), + (30, PRESSURE_INHG, pytest.approx(1015.9167), PRESSURE_MBAR), + (30, PRESSURE_INHG, pytest.approx(101.59167), PRESSURE_CBAR), + (30, PRESSURE_INHG, pytest.approx(762.002), PRESSURE_MMHG), + (30, PRESSURE_MMHG, pytest.approx(0.580102), PRESSURE_PSI), + (30, PRESSURE_MMHG, pytest.approx(3.99966), PRESSURE_KPA), + (30, PRESSURE_MMHG, pytest.approx(39.9966), PRESSURE_HPA), + (30, PRESSURE_MMHG, pytest.approx(3999.66), PRESSURE_PA), + (30, PRESSURE_MMHG, pytest.approx(39.9966), PRESSURE_MBAR), + (30, PRESSURE_MMHG, pytest.approx(3.99966), PRESSURE_CBAR), + (30, PRESSURE_MMHG, pytest.approx(1.181099), PRESSURE_INHG), + ], +) +def test_pressure_convert( + value: float, + from_unit: str, + expected: float, + to_unit: str, +) -> None: + """Test conversion to other units.""" + assert PressureConverter.convert(value, from_unit, to_unit) == expected + + +@pytest.mark.parametrize( + "value,from_unit,expected,to_unit", + [ + (5, VOLUME_LITERS, pytest.approx(1.32086), VOLUME_GALLONS), + (5, VOLUME_GALLONS, pytest.approx(18.92706), VOLUME_LITERS), + (5, VOLUME_CUBIC_METERS, pytest.approx(176.5733335), VOLUME_CUBIC_FEET), + (500, VOLUME_CUBIC_FEET, pytest.approx(14.1584233), VOLUME_CUBIC_METERS), + (500, VOLUME_CUBIC_FEET, pytest.approx(14.1584233), VOLUME_CUBIC_METERS), + (500, VOLUME_CUBIC_FEET, pytest.approx(478753.2467), VOLUME_FLUID_OUNCE), + (500, VOLUME_CUBIC_FEET, pytest.approx(3740.25974), VOLUME_GALLONS), + (500, VOLUME_CUBIC_FEET, pytest.approx(14158.42329599), VOLUME_LITERS), + (500, VOLUME_CUBIC_FEET, pytest.approx(14158423.29599), VOLUME_MILLILITERS), + (500, VOLUME_CUBIC_METERS, 500, VOLUME_CUBIC_METERS), + (500, VOLUME_CUBIC_METERS, pytest.approx(16907011.35), VOLUME_FLUID_OUNCE), + (500, VOLUME_CUBIC_METERS, pytest.approx(132086.02617), VOLUME_GALLONS), + (500, VOLUME_CUBIC_METERS, 500000, VOLUME_LITERS), + (500, VOLUME_CUBIC_METERS, 500000000, VOLUME_MILLILITERS), + (500, VOLUME_FLUID_OUNCE, pytest.approx(0.52218967), VOLUME_CUBIC_FEET), + (500, VOLUME_FLUID_OUNCE, pytest.approx(0.014786764), VOLUME_CUBIC_METERS), + (500, VOLUME_FLUID_OUNCE, 3.90625, VOLUME_GALLONS), + (500, VOLUME_FLUID_OUNCE, pytest.approx(14.786764), VOLUME_LITERS), + (500, VOLUME_FLUID_OUNCE, pytest.approx(14786.764), VOLUME_MILLILITERS), + (500, VOLUME_GALLONS, pytest.approx(66.84027), VOLUME_CUBIC_FEET), + (500, VOLUME_GALLONS, pytest.approx(1.892706), VOLUME_CUBIC_METERS), + (500, VOLUME_GALLONS, 64000, VOLUME_FLUID_OUNCE), + (500, VOLUME_GALLONS, pytest.approx(1892.70589), VOLUME_LITERS), + (500, VOLUME_GALLONS, pytest.approx(1892705.89), VOLUME_MILLILITERS), + (500, VOLUME_LITERS, pytest.approx(17.65733), VOLUME_CUBIC_FEET), + (500, VOLUME_LITERS, 0.5, VOLUME_CUBIC_METERS), + (500, VOLUME_LITERS, pytest.approx(16907.011), VOLUME_FLUID_OUNCE), + (500, VOLUME_LITERS, pytest.approx(132.086), VOLUME_GALLONS), + (500, VOLUME_LITERS, 500000, VOLUME_MILLILITERS), + (500, VOLUME_MILLILITERS, pytest.approx(0.01765733), VOLUME_CUBIC_FEET), + (500, VOLUME_MILLILITERS, 0.0005, VOLUME_CUBIC_METERS), + (500, VOLUME_MILLILITERS, pytest.approx(16.907), VOLUME_FLUID_OUNCE), + (500, VOLUME_MILLILITERS, pytest.approx(0.132086), VOLUME_GALLONS), + (500, VOLUME_MILLILITERS, 0.5, VOLUME_LITERS), + ], +) +def test_volume_convert( + value: float, + from_unit: str, + expected: float, + to_unit: str, +) -> None: + """Test conversion to other units.""" + assert VolumeConverter.convert(value, from_unit, to_unit) == expected