From eb469d6a2f834a7ecc90f738d911a6867428bf0b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 6 Apr 2023 14:55:51 +1200 Subject: [PATCH] Move enum mapper to own file to prevent circular dependency (#90890) * Move enum_mapper to own file to prevent circular dependency * Add enum mapper test --- homeassistant/components/esphome/__init__.py | 39 +---------------- homeassistant/components/esphome/climate.py | 8 +--- .../components/esphome/enum_mapper.py | 39 +++++++++++++++++ homeassistant/components/esphome/fan.py | 8 +--- .../components/esphome/media_player.py | 8 +--- homeassistant/components/esphome/number.py | 8 +--- homeassistant/components/esphome/sensor.py | 8 +--- tests/components/esphome/test_enum_mapper.py | 42 +++++++++++++++++++ 8 files changed, 93 insertions(+), 67 deletions(-) create mode 100644 homeassistant/components/esphome/enum_mapper.py create mode 100644 tests/components/esphome/test_enum_mapper.py diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index 58659a671f0..d3852c3bcc3 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -5,12 +5,11 @@ from collections.abc import Callable import functools import logging import math -from typing import Any, Generic, NamedTuple, TypeVar, cast, overload +from typing import Any, Generic, NamedTuple, TypeVar, cast from aioesphomeapi import ( APIClient, APIConnectionError, - APIIntEnum, APIVersion, DeviceInfo as EsphomeDeviceInfo, EntityCategory as EsphomeEntityCategory, @@ -64,6 +63,7 @@ from .domain_data import DomainData # Import config flow so that it's added to the registry from .entry_data import RuntimeEntryData +from .enum_mapper import EsphomeEnumMapper CONF_DEVICE_NAME = "device_name" CONF_NOISE_PSK = "noise_psk" @@ -687,41 +687,6 @@ def esphome_state_property( return _wrapper -_EnumT = TypeVar("_EnumT", bound=APIIntEnum) -_ValT = TypeVar("_ValT") - - -class EsphomeEnumMapper(Generic[_EnumT, _ValT]): - """Helper class to convert between hass and esphome enum values.""" - - def __init__(self, mapping: dict[_EnumT, _ValT]) -> None: - """Construct a EsphomeEnumMapper.""" - # Add none mapping - augmented_mapping: dict[ - _EnumT | None, _ValT | None - ] = mapping # type: ignore[assignment] - augmented_mapping[None] = None - - self._mapping = augmented_mapping - self._inverse: dict[_ValT, _EnumT] = {v: k for k, v in mapping.items()} - - @overload - def from_esphome(self, value: _EnumT) -> _ValT: - ... - - @overload - def from_esphome(self, value: _EnumT | None) -> _ValT | None: - ... - - def from_esphome(self, value: _EnumT | None) -> _ValT | None: - """Convert from an esphome int representation to a hass string.""" - return self._mapping[value] - - def from_hass(self, value: _ValT) -> _EnumT: - """Convert from a hass string to a esphome int representation.""" - return self._inverse[value] - - ICON_SCHEMA = vol.Schema(cv.icon) diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index bfb3dbb8668..e40df234d58 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -54,12 +54,8 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ( - EsphomeEntity, - EsphomeEnumMapper, - esphome_state_property, - platform_async_setup_entry, -) +from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .enum_mapper import EsphomeEnumMapper FAN_QUIET = "quiet" diff --git a/homeassistant/components/esphome/enum_mapper.py b/homeassistant/components/esphome/enum_mapper.py new file mode 100644 index 00000000000..566f0bc503b --- /dev/null +++ b/homeassistant/components/esphome/enum_mapper.py @@ -0,0 +1,39 @@ +"""Helper class to convert between Home Assistant and ESPHome enum values.""" + +from typing import Generic, TypeVar, overload + +from aioesphomeapi import APIIntEnum + +_EnumT = TypeVar("_EnumT", bound=APIIntEnum) +_ValT = TypeVar("_ValT") + + +class EsphomeEnumMapper(Generic[_EnumT, _ValT]): + """Helper class to convert between hass and esphome enum values.""" + + def __init__(self, mapping: dict[_EnumT, _ValT]) -> None: + """Construct a EsphomeEnumMapper.""" + # Add none mapping + augmented_mapping: dict[ + _EnumT | None, _ValT | None + ] = mapping # type: ignore[assignment] + augmented_mapping[None] = None + + self._mapping = augmented_mapping + self._inverse: dict[_ValT, _EnumT] = {v: k for k, v in mapping.items()} + + @overload + def from_esphome(self, value: _EnumT) -> _ValT: + ... + + @overload + def from_esphome(self, value: _EnumT | None) -> _ValT | None: + ... + + def from_esphome(self, value: _EnumT | None) -> _ValT | None: + """Convert from an esphome int representation to a hass string.""" + return self._mapping[value] + + def from_hass(self, value: _ValT) -> _EnumT: + """Convert from a hass string to a esphome int representation.""" + return self._inverse[value] diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 27952d36c60..01060630964 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -22,12 +22,8 @@ from homeassistant.util.percentage import ( ranged_value_to_percentage, ) -from . import ( - EsphomeEntity, - EsphomeEnumMapper, - esphome_state_property, - platform_async_setup_entry, -) +from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .enum_mapper import EsphomeEnumMapper ORDERED_NAMED_FAN_SPEEDS = [FanSpeed.LOW, FanSpeed.MEDIUM, FanSpeed.HIGH] diff --git a/homeassistant/components/esphome/media_player.py b/homeassistant/components/esphome/media_player.py index 673a90580e0..d818e040965 100644 --- a/homeassistant/components/esphome/media_player.py +++ b/homeassistant/components/esphome/media_player.py @@ -24,12 +24,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ( - EsphomeEntity, - EsphomeEnumMapper, - esphome_state_property, - platform_async_setup_entry, -) +from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .enum_mapper import EsphomeEnumMapper async def async_setup_entry( diff --git a/homeassistant/components/esphome/number.py b/homeassistant/components/esphome/number.py index 7379be33da2..3ca8e0b9728 100644 --- a/homeassistant/components/esphome/number.py +++ b/homeassistant/components/esphome/number.py @@ -11,12 +11,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.enum import try_parse_enum -from . import ( - EsphomeEntity, - EsphomeEnumMapper, - esphome_state_property, - platform_async_setup_entry, -) +from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .enum_mapper import EsphomeEnumMapper async def async_setup_entry( diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 863096cb3b1..25a0bfaff7f 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -24,12 +24,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt from homeassistant.util.enum import try_parse_enum -from . import ( - EsphomeEntity, - EsphomeEnumMapper, - esphome_state_property, - platform_async_setup_entry, -) +from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .enum_mapper import EsphomeEnumMapper async def async_setup_entry( diff --git a/tests/components/esphome/test_enum_mapper.py b/tests/components/esphome/test_enum_mapper.py new file mode 100644 index 00000000000..52b81bb3836 --- /dev/null +++ b/tests/components/esphome/test_enum_mapper.py @@ -0,0 +1,42 @@ +"""Test ESPHome enum mapper.""" + +from aioesphomeapi import APIIntEnum + +from homeassistant.backports.enum import StrEnum +from homeassistant.components.esphome.enum_mapper import EsphomeEnumMapper + + +class MockEnum(APIIntEnum): + """Mock enum.""" + + ESPHOME_FOO = 1 + ESPHOME_BAR = 2 + + +class MockStrEnum(StrEnum): + """Mock enum.""" + + HA_FOO = "foo" + HA_BAR = "bar" + + +MOCK_MAPPING: EsphomeEnumMapper[MockEnum, MockStrEnum] = EsphomeEnumMapper( + { + MockEnum.ESPHOME_FOO: MockStrEnum.HA_FOO, + MockEnum.ESPHOME_BAR: MockStrEnum.HA_BAR, + } +) + + +async def test_map_esphome_to_ha() -> None: + """Test mapping from ESPHome to HA.""" + + assert MOCK_MAPPING.from_esphome(MockEnum.ESPHOME_FOO) == MockStrEnum.HA_FOO + assert MOCK_MAPPING.from_esphome(MockEnum.ESPHOME_BAR) == MockStrEnum.HA_BAR + + +async def test_map_ha_to_esphome() -> None: + """Test mapping from HA to ESPHome.""" + + assert MOCK_MAPPING.from_hass(MockStrEnum.HA_FOO) == MockEnum.ESPHOME_FOO + assert MOCK_MAPPING.from_hass(MockStrEnum.HA_BAR) == MockEnum.ESPHOME_BAR