<commit_before>"""Support for RainMachine selects."""
from __future__ import annotations

from dataclasses import dataclass

from regenmaschine.errors import RainMachineError

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_UNIT_SYSTEM_IMPERIAL
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import RainMachineData, RainMachineEntity
from .const import DATA_RESTRICTIONS_UNIVERSAL, DOMAIN
from .model import (
    RainMachineEntityDescription,
    RainMachineEntityDescriptionMixinDataKey,
)
from .util import key_exists


@dataclass
class RainMachineSelectDescription(
    SelectEntityDescription,
    RainMachineEntityDescription,
    RainMachineEntityDescriptionMixinDataKey,
):
    """Describe a generic RainMachine select."""


@dataclass
class FreezeProtectionSelectOption:
    """Define an option for a freeze selection select."""

    api_value: float
    imperial_label: str
    metric_label: str


@dataclass
class FreezeProtectionTemperatureMixin:
    """Define an entity description mixin to include an options list."""

    extended_options: list[FreezeProtectionSelectOption]


@dataclass
class FreezeProtectionSelectDescription(
    RainMachineSelectDescription, FreezeProtectionTemperatureMixin
):
    """Describe a freeze protection temperature select."""


TYPE_FREEZE_PROTECTION_TEMPERATURE = "freeze_protection_temperature"

SELECT_DESCRIPTIONS = (
    FreezeProtectionSelectDescription(
        key=TYPE_FREEZE_PROTECTION_TEMPERATURE,
        name="Freeze protection temperature",
        icon="mdi:thermometer",
        entity_category=EntityCategory.CONFIG,
        api_category=DATA_RESTRICTIONS_UNIVERSAL,
        data_key="freezeProtectTemp",
        extended_options=[
            FreezeProtectionSelectOption(
                api_value=0.0,
                imperial_label="32°F",
                metric_label="0°C",
            ),
            FreezeProtectionSelectOption(
                api_value=2.0,
                imperial_label="35.6°F",
                metric_label="2°C",
            ),
            FreezeProtectionSelectOption(
                api_value=5.0,
                imperial_label="41°F",
                metric_label="5°C",
            ),
            FreezeProtectionSelectOption(
                api_value=10.0,
                imperial_label="50°F",
                metric_label="10°C",
            ),
        ],
    ),
)


async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
    """Set up RainMachine selects based on a config entry."""
    data: RainMachineData = hass.data[DOMAIN][entry.entry_id]

    entity_map = {
        TYPE_FREEZE_PROTECTION_TEMPERATURE: FreezeProtectionTemperatureSelect,
    }

    async_add_entities(
        entity_map[description.key](entry, data, description, hass.config.units.name)
        for description in SELECT_DESCRIPTIONS
        if (
            (coordinator := data.coordinators[description.api_category]) is not None
            and coordinator.data
            and key_exists(coordinator.data, description.data_key)
        )
    )


class FreezeProtectionTemperatureSelect(RainMachineEntity, SelectEntity):
    """Define a RainMachine select."""

    entity_description: FreezeProtectionSelectDescription

    def __init__(
        self,
        entry: ConfigEntry,
        data: RainMachineData,
        description: FreezeProtectionSelectDescription,
        unit_system: str,
    ) -> None:
        """Initialize."""
        super().__init__(entry, data, description)

        self._api_value_to_label_map = {}
        self._label_to_api_value_map = {}

        for option in description.extended_options:
            if unit_system == CONF_UNIT_SYSTEM_IMPERIAL:
                label = option.imperial_label
            else:
                label = option.metric_label
            self._api_value_to_label_map[option.api_value] = label
            self._label_to_api_value_map[label] = option.api_value

        self._attr_options = list(self._label_to_api_value_map)

    async def async_select_option(self, option: str) -> None:
        """Change the selected option."""
        try:
            await self._data.controller.restrictions.set_universal(
                {self.entity_description.data_key: self._label_to_api_value_map[option]}
            )
        except RainMachineError as err:
            raise ValueError(f"Error while setting {self.name}: {err}") from err

    @callback
    def update_from_latest_data(self) -> None:
        """Update the entity when new data is received."""
        raw_value = self.coordinator.data[self.entity_description.data_key]
        self._attr_current_option = self._api_value_to_label_map[raw_value]
<commit_msg>Use correct exception type for RainMachine select API error (#79309)<commit_after>"""Support for RainMachine selects."""
from __future__ import annotations

from dataclasses import dataclass

from regenmaschine.errors import RainMachineError

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_UNIT_SYSTEM_IMPERIAL
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import RainMachineData, RainMachineEntity
from .const import DATA_RESTRICTIONS_UNIVERSAL, DOMAIN
from .model import (
    RainMachineEntityDescription,
    RainMachineEntityDescriptionMixinDataKey,
)
from .util import key_exists


@dataclass
class RainMachineSelectDescription(
    SelectEntityDescription,
    RainMachineEntityDescription,
    RainMachineEntityDescriptionMixinDataKey,
):
    """Describe a generic RainMachine select."""


@dataclass
class FreezeProtectionSelectOption:
    """Define an option for a freeze selection select."""

    api_value: float
    imperial_label: str
    metric_label: str


@dataclass
class FreezeProtectionTemperatureMixin:
    """Define an entity description mixin to include an options list."""

    extended_options: list[FreezeProtectionSelectOption]


@dataclass
class FreezeProtectionSelectDescription(
    RainMachineSelectDescription, FreezeProtectionTemperatureMixin
):
    """Describe a freeze protection temperature select."""


TYPE_FREEZE_PROTECTION_TEMPERATURE = "freeze_protection_temperature"

SELECT_DESCRIPTIONS = (
    FreezeProtectionSelectDescription(
        key=TYPE_FREEZE_PROTECTION_TEMPERATURE,
        name="Freeze protection temperature",
        icon="mdi:thermometer",
        entity_category=EntityCategory.CONFIG,
        api_category=DATA_RESTRICTIONS_UNIVERSAL,
        data_key="freezeProtectTemp",
        extended_options=[
            FreezeProtectionSelectOption(
                api_value=0.0,
                imperial_label="32°F",
                metric_label="0°C",
            ),
            FreezeProtectionSelectOption(
                api_value=2.0,
                imperial_label="35.6°F",
                metric_label="2°C",
            ),
            FreezeProtectionSelectOption(
                api_value=5.0,
                imperial_label="41°F",
                metric_label="5°C",
            ),
            FreezeProtectionSelectOption(
                api_value=10.0,
                imperial_label="50°F",
                metric_label="10°C",
            ),
        ],
    ),
)


async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
    """Set up RainMachine selects based on a config entry."""
    data: RainMachineData = hass.data[DOMAIN][entry.entry_id]

    entity_map = {
        TYPE_FREEZE_PROTECTION_TEMPERATURE: FreezeProtectionTemperatureSelect,
    }

    async_add_entities(
        entity_map[description.key](entry, data, description, hass.config.units.name)
        for description in SELECT_DESCRIPTIONS
        if (
            (coordinator := data.coordinators[description.api_category]) is not None
            and coordinator.data
            and key_exists(coordinator.data, description.data_key)
        )
    )


class FreezeProtectionTemperatureSelect(RainMachineEntity, SelectEntity):
    """Define a RainMachine select."""

    entity_description: FreezeProtectionSelectDescription

    def __init__(
        self,
        entry: ConfigEntry,
        data: RainMachineData,
        description: FreezeProtectionSelectDescription,
        unit_system: str,
    ) -> None:
        """Initialize."""
        super().__init__(entry, data, description)

        self._api_value_to_label_map = {}
        self._label_to_api_value_map = {}

        for option in description.extended_options:
            if unit_system == CONF_UNIT_SYSTEM_IMPERIAL:
                label = option.imperial_label
            else:
                label = option.metric_label
            self._api_value_to_label_map[option.api_value] = label
            self._label_to_api_value_map[label] = option.api_value

        self._attr_options = list(self._label_to_api_value_map)

    async def async_select_option(self, option: str) -> None:
        """Change the selected option."""
        try:
            await self._data.controller.restrictions.set_universal(
                {self.entity_description.data_key: self._label_to_api_value_map[option]}
            )
        except RainMachineError as err:
            raise HomeAssistantError(f"Error while setting {self.name}: {err}") from err

    @callback
    def update_from_latest_data(self) -> None:
        """Update the entity when new data is received."""
        raw_value = self.coordinator.data[self.entity_description.data_key]
        self._attr_current_option = self._api_value_to_label_map[raw_value]

{'fraction_non_alphanumeric': 0.0748641975308642, 'fraction_numerical': 0.005037037037037037, 'mean_word_length': 3.0748490945674045, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3740253', 'n_tokens_mistral': 2810, 'n_tokens_neox': 2725, 'n_words': 565}