mirror of
https://github.com/home-assistant/core.git
synced 2026-05-29 20:23:24 +02:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bb5483a7e | |||
| ac94e9c8d8 | |||
| 6c1c8135d9 | |||
| dc9a3f6a9d | |||
| 74b88c276f | |||
| 931b260088 | |||
| fd1cce8fd9 | |||
| d24b000368 | |||
| 19ca4caba9 | |||
| 94263dca40 | |||
| f338761f28 | |||
| 6a9c21fc74 |
@@ -26,6 +26,8 @@ from .const import (
|
||||
SERVICE_SELECT_NEXT,
|
||||
SERVICE_SELECT_OPTION,
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
SelectEntityAttribute,
|
||||
SelectServiceArgument,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -51,7 +53,9 @@ __all__ = [
|
||||
"SERVICE_SELECT_OPTION",
|
||||
"SERVICE_SELECT_PREVIOUS",
|
||||
"SelectEntity",
|
||||
"SelectEntityAttribute",
|
||||
"SelectEntityDescription",
|
||||
"SelectServiceArgument",
|
||||
]
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
@@ -78,19 +82,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SELECT_NEXT,
|
||||
{vol.Optional(ATTR_CYCLE, default=True): bool},
|
||||
{vol.Optional(SelectServiceArgument.CYCLE, default=True): bool},
|
||||
SelectEntity.async_next.__name__,
|
||||
)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SELECT_OPTION,
|
||||
{vol.Required(ATTR_OPTION): cv.string},
|
||||
{vol.Required(SelectServiceArgument.OPTION): cv.string},
|
||||
SelectEntity.async_handle_select_option.__name__,
|
||||
)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
{vol.Optional(ATTR_CYCLE, default=True): bool},
|
||||
{vol.Optional(SelectServiceArgument.CYCLE, default=True): bool},
|
||||
SelectEntity.async_previous.__name__,
|
||||
)
|
||||
|
||||
@@ -122,7 +126,7 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
|
||||
class SelectEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Representation of a Select entity."""
|
||||
|
||||
_entity_component_unrecorded_attributes = frozenset({ATTR_OPTIONS})
|
||||
_entity_component_unrecorded_attributes = frozenset({SelectEntityAttribute.OPTIONS})
|
||||
|
||||
entity_description: SelectEntityDescription
|
||||
_attr_current_option: str | None = None
|
||||
@@ -133,7 +137,7 @@ class SelectEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def capability_attributes(self) -> dict[str, Any]:
|
||||
"""Return capability attributes."""
|
||||
return {
|
||||
ATTR_OPTIONS: self.options,
|
||||
SelectEntityAttribute.OPTIONS.value: self.options,
|
||||
}
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
"""Provides the constants needed for the component."""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
DOMAIN = "select"
|
||||
|
||||
ATTR_CYCLE = "cycle"
|
||||
ATTR_OPTIONS = "options"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_OPTION = "option"
|
||||
|
||||
class SelectEntityAttribute(StrEnum):
|
||||
"""Select entity attributes."""
|
||||
|
||||
OPTIONS = "options"
|
||||
|
||||
|
||||
class SelectServiceArgument:
|
||||
"""Select service arguments."""
|
||||
|
||||
CYCLE = "cycle"
|
||||
OPTION = "option"
|
||||
|
||||
|
||||
CONF_CYCLE = "cycle"
|
||||
CONF_OPTION = "option"
|
||||
|
||||
#
|
||||
# Deprecated constants
|
||||
# They are single-use constants, or have been replaced by enums.
|
||||
# They need to be formally deprecated when all usage is removed
|
||||
# from core components
|
||||
#
|
||||
ATTR_CYCLE = SelectServiceArgument.CYCLE
|
||||
ATTR_OPTION = SelectServiceArgument.OPTION
|
||||
ATTR_OPTIONS = SelectEntityAttribute.OPTIONS.value
|
||||
SERVICE_SELECT_FIRST = "select_first"
|
||||
SERVICE_SELECT_LAST = "select_last"
|
||||
SERVICE_SELECT_NEXT = "select_next"
|
||||
|
||||
@@ -22,9 +22,6 @@ from homeassistant.helpers.entity import get_capability
|
||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
|
||||
from .const import (
|
||||
ATTR_CYCLE,
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
CONF_CYCLE,
|
||||
CONF_OPTION,
|
||||
DOMAIN,
|
||||
@@ -33,6 +30,8 @@ from .const import (
|
||||
SERVICE_SELECT_NEXT,
|
||||
SERVICE_SELECT_OPTION,
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
SelectEntityAttribute,
|
||||
SelectServiceArgument,
|
||||
)
|
||||
|
||||
_ACTION_SCHEMA = vol.Any(
|
||||
@@ -112,9 +111,9 @@ async def async_call_action_from_config(
|
||||
"""Execute a device action."""
|
||||
service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]}
|
||||
if config[CONF_TYPE] == SERVICE_SELECT_OPTION:
|
||||
service_data[ATTR_OPTION] = config[CONF_OPTION]
|
||||
service_data[SelectServiceArgument.OPTION] = config[CONF_OPTION]
|
||||
if config[CONF_TYPE] in {SERVICE_SELECT_NEXT, SERVICE_SELECT_PREVIOUS}:
|
||||
service_data[ATTR_CYCLE] = config[CONF_CYCLE]
|
||||
service_data[SelectServiceArgument.CYCLE] = config[CONF_CYCLE]
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
@@ -142,7 +141,10 @@ async def async_get_action_capabilities(
|
||||
entry = async_get_entity_registry_entry_or_raise(
|
||||
hass, config[CONF_ENTITY_ID]
|
||||
)
|
||||
options = get_capability(hass, entry.entity_id, ATTR_OPTIONS) or []
|
||||
options = (
|
||||
get_capability(hass, entry.entity_id, SelectEntityAttribute.OPTIONS)
|
||||
or []
|
||||
)
|
||||
return {
|
||||
"extra_fields": vol.Schema({vol.Required(CONF_OPTION): vol.In(options)})
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA
|
||||
from homeassistant.helpers.entity import get_capability
|
||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
|
||||
from .const import ATTR_OPTIONS, CONF_OPTION, DOMAIN
|
||||
from .const import CONF_OPTION, DOMAIN, SelectEntityAttribute
|
||||
|
||||
# nypy: disallow-any-generics
|
||||
|
||||
@@ -84,7 +84,9 @@ async def async_get_condition_capabilities(
|
||||
|
||||
try:
|
||||
entry = async_get_entity_registry_entry_or_raise(hass, config[CONF_ENTITY_ID])
|
||||
options = get_capability(hass, entry.entity_id, ATTR_OPTIONS) or []
|
||||
options = (
|
||||
get_capability(hass, entry.entity_id, SelectEntityAttribute.OPTIONS) or []
|
||||
)
|
||||
except HomeAssistantError:
|
||||
options = []
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from homeassistant.helpers.entity import get_capability
|
||||
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import ATTR_OPTIONS, DOMAIN
|
||||
from .const import DOMAIN, SelectEntityAttribute
|
||||
|
||||
TRIGGER_TYPES = {"current_option_changed"}
|
||||
|
||||
@@ -94,7 +94,9 @@ async def async_get_trigger_capabilities(
|
||||
|
||||
try:
|
||||
entry = async_get_entity_registry_entry_or_raise(hass, config[CONF_ENTITY_ID])
|
||||
options = get_capability(hass, entry.entity_id, ATTR_OPTIONS) or []
|
||||
options = (
|
||||
get_capability(hass, entry.entity_id, SelectEntityAttribute.OPTIONS) or []
|
||||
)
|
||||
except HomeAssistantError:
|
||||
options = []
|
||||
|
||||
|
||||
@@ -8,7 +8,12 @@ from typing import Any
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import Context, HomeAssistant, State
|
||||
|
||||
from .const import ATTR_OPTION, ATTR_OPTIONS, DOMAIN, SERVICE_SELECT_OPTION
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
SelectEntityAttribute,
|
||||
SelectServiceArgument,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -25,7 +30,7 @@ async def _async_reproduce_state(
|
||||
_LOGGER.warning("Unable to find entity %s", state.entity_id)
|
||||
return
|
||||
|
||||
if state.state not in cur_state.attributes.get(ATTR_OPTIONS, []):
|
||||
if state.state not in cur_state.attributes.get(SelectEntityAttribute.OPTIONS, []):
|
||||
_LOGGER.warning(
|
||||
"Invalid state specified for %s: %s", state.entity_id, state.state
|
||||
)
|
||||
@@ -38,7 +43,7 @@ async def _async_reproduce_state(
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_ENTITY_ID: state.entity_id, ATTR_OPTION: state.state},
|
||||
{ATTR_ENTITY_ID: state.entity_id, SelectServiceArgument.OPTION: state.state},
|
||||
context=context,
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Common helpers for select entity component tests."""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
|
||||
from tests.common import MockEntity
|
||||
@@ -21,3 +23,13 @@ class MockSelectEntity(MockEntity, SelectEntity):
|
||||
def select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
self._values["current_option"] = option
|
||||
|
||||
|
||||
class SelectService(StrEnum):
|
||||
"""Select services."""
|
||||
|
||||
SELECT_FIRST = "select_first"
|
||||
SELECT_LAST = "select_last"
|
||||
SELECT_NEXT = "select_next"
|
||||
SELECT_OPTION = "select_option"
|
||||
SELECT_PREVIOUS = "select_previous"
|
||||
|
||||
@@ -5,22 +5,18 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.select import (
|
||||
ATTR_CYCLE,
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_FIRST,
|
||||
SERVICE_SELECT_LAST,
|
||||
SERVICE_SELECT_NEXT,
|
||||
SERVICE_SELECT_OPTION,
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
SelectEntity,
|
||||
SelectEntityAttribute,
|
||||
SelectServiceArgument,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import SelectService
|
||||
|
||||
from tests.common import setup_test_component_platform
|
||||
|
||||
|
||||
@@ -85,7 +81,7 @@ async def test_select(hass: HomeAssistant) -> None:
|
||||
|
||||
assert select.select_option.call_count == 5
|
||||
|
||||
assert select.capability_attributes[ATTR_OPTIONS] == [
|
||||
assert select.capability_attributes[SelectEntityAttribute.OPTIONS] == [
|
||||
"option_one",
|
||||
"option_two",
|
||||
"option_three",
|
||||
@@ -106,8 +102,11 @@ async def test_custom_integration_and_validation(
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_OPTION: "option 2", ATTR_ENTITY_ID: "select.select_1"},
|
||||
SelectService.SELECT_OPTION,
|
||||
{
|
||||
SelectServiceArgument.OPTION: "option 2",
|
||||
ATTR_ENTITY_ID: "select.select_1",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@@ -119,8 +118,11 @@ async def test_custom_integration_and_validation(
|
||||
with pytest.raises(ServiceValidationError) as exc:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_OPTION: "option invalid", ATTR_ENTITY_ID: "select.select_1"},
|
||||
SelectService.SELECT_OPTION,
|
||||
{
|
||||
SelectServiceArgument.OPTION: "option invalid",
|
||||
ATTR_ENTITY_ID: "select.select_1",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -134,8 +136,11 @@ async def test_custom_integration_and_validation(
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_OPTION: "option invalid", ATTR_ENTITY_ID: "select.select_2"},
|
||||
SelectService.SELECT_OPTION,
|
||||
{
|
||||
SelectServiceArgument.OPTION: "option invalid",
|
||||
ATTR_ENTITY_ID: "select.select_2",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -143,8 +148,11 @@ async def test_custom_integration_and_validation(
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_OPTION: "option 3", ATTR_ENTITY_ID: "select.select_2"},
|
||||
SelectService.SELECT_OPTION,
|
||||
{
|
||||
SelectServiceArgument.OPTION: "option 3",
|
||||
ATTR_ENTITY_ID: "select.select_2",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -153,7 +161,7 @@ async def test_custom_integration_and_validation(
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_FIRST,
|
||||
SelectService.SELECT_FIRST,
|
||||
{ATTR_ENTITY_ID: "select.select_2"},
|
||||
blocking=True,
|
||||
)
|
||||
@@ -161,7 +169,7 @@ async def test_custom_integration_and_validation(
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_LAST,
|
||||
SelectService.SELECT_LAST,
|
||||
{ATTR_ENTITY_ID: "select.select_2"},
|
||||
blocking=True,
|
||||
)
|
||||
@@ -170,8 +178,11 @@ async def test_custom_integration_and_validation(
|
||||
# Do no cycle
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_NEXT,
|
||||
{ATTR_ENTITY_ID: "select.select_2", ATTR_CYCLE: False},
|
||||
SelectService.SELECT_NEXT,
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.select_2",
|
||||
SelectServiceArgument.CYCLE: False,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert hass.states.get("select.select_2").state == "option 3"
|
||||
@@ -179,7 +190,7 @@ async def test_custom_integration_and_validation(
|
||||
# Do cycle (default behavior)
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_NEXT,
|
||||
SelectService.SELECT_NEXT,
|
||||
{ATTR_ENTITY_ID: "select.select_2"},
|
||||
blocking=True,
|
||||
)
|
||||
@@ -188,8 +199,11 @@ async def test_custom_integration_and_validation(
|
||||
# Do not cycle
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
{ATTR_ENTITY_ID: "select.select_2", ATTR_CYCLE: False},
|
||||
SelectService.SELECT_PREVIOUS,
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.select_2",
|
||||
SelectServiceArgument.CYCLE: False,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert hass.states.get("select.select_2").state == "option 1"
|
||||
@@ -197,7 +211,7 @@ async def test_custom_integration_and_validation(
|
||||
# Do cycle (default behavior)
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
SelectService.SELECT_PREVIOUS,
|
||||
{ATTR_ENTITY_ID: "select.select_2"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import pytest
|
||||
from homeassistant.components import select
|
||||
from homeassistant.components.recorder import Recorder
|
||||
from homeassistant.components.recorder.history import get_significant_states
|
||||
from homeassistant.components.select import ATTR_OPTIONS
|
||||
from homeassistant.components.select import SelectEntityAttribute
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
@@ -46,5 +46,5 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant)
|
||||
assert len(states) >= 1
|
||||
for entity_states in states.values():
|
||||
for state in entity_states:
|
||||
assert ATTR_OPTIONS not in state.attributes
|
||||
assert SelectEntityAttribute.OPTIONS not in state.attributes
|
||||
assert ATTR_FRIENDLY_NAME in state.attributes
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.select.const import (
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
SelectEntityAttribute,
|
||||
SelectServiceArgument,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.state import async_reproduce_state
|
||||
|
||||
from .common import SelectService
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
@@ -19,11 +20,11 @@ async def test_reproducing_states(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test reproducing select states."""
|
||||
calls = async_mock_service(hass, DOMAIN, SERVICE_SELECT_OPTION)
|
||||
calls = async_mock_service(hass, DOMAIN, SelectService.SELECT_OPTION)
|
||||
hass.states.async_set(
|
||||
"select.test",
|
||||
"option_one",
|
||||
{ATTR_OPTIONS: ["option_one", "option_two", "option_three"]},
|
||||
{SelectEntityAttribute.OPTIONS: ["option_one", "option_two", "option_three"]},
|
||||
)
|
||||
|
||||
await async_reproduce_state(
|
||||
@@ -35,7 +36,10 @@ async def test_reproducing_states(
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].domain == DOMAIN
|
||||
assert calls[0].data == {ATTR_ENTITY_ID: "select.test", ATTR_OPTION: "option_two"}
|
||||
assert calls[0].data == {
|
||||
ATTR_ENTITY_ID: "select.test",
|
||||
SelectServiceArgument.OPTION: "option_two",
|
||||
}
|
||||
|
||||
# Calling it again should not do anything
|
||||
await async_reproduce_state(
|
||||
|
||||
Reference in New Issue
Block a user