1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Allow area, device, and entity selectors to optionally support multiple selections like target selector (#63138)

* Allow area, device, and entity selectors to optionally support multiple selections like target selector

* Update according to code review comments

* Adjust tests

* Update according to review comments

* Tweak error message for multiple entities

Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
Richard T. Schaefer
2022-03-03 03:35:06 -06:00
committed by GitHub
parent 58c00da8a0
commit 15580281a3
4 changed files with 90 additions and 44 deletions

View File

@@ -8,7 +8,7 @@ from typing import Any, cast
import voluptuous as vol
from homeassistant.const import CONF_MODE, CONF_UNIT_OF_MEASUREMENT
from homeassistant.core import split_entity_id
from homeassistant.core import split_entity_id, valid_entity_id
from homeassistant.util import decorator
from . import config_validation as cv
@@ -74,44 +74,54 @@ class Selector:
return {"selector": {self.selector_type: self.config}}
SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA = vol.Schema(
{
# Integration that provided the entity
vol.Optional("integration"): str,
# Domain the entity belongs to
vol.Optional("domain"): str,
# Device class of the entity
vol.Optional("device_class"): str,
}
)
@SELECTORS.register("entity")
class EntitySelector(Selector):
"""Selector of a single entity."""
"""Selector of a single or list of entities."""
selector_type = "entity"
CONFIG_SCHEMA = vol.Schema(
{
# Integration that provided the entity
vol.Optional("integration"): str,
# Domain the entity belongs to
vol.Optional("domain"): str,
# Device class of the entity
vol.Optional("device_class"): str,
}
CONFIG_SCHEMA = SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA.extend(
{vol.Optional("multiple", default=False): cv.boolean}
)
def __call__(self, data: Any) -> str:
def __call__(self, data: Any) -> str | list[str]:
"""Validate the passed selection."""
try:
entity_id = cv.entity_id(data)
domain = split_entity_id(entity_id)[0]
except vol.Invalid:
# Not a valid entity_id, maybe it's an entity entry id
return cv.entity_id_or_uuid(cv.string(data))
else:
if "domain" in self.config and domain != self.config["domain"]:
raise vol.Invalid(
f"Entity {entity_id} belongs to domain {domain}, "
f"expected {self.config['domain']}"
)
return entity_id
def validate(e_or_u: str) -> str:
e_or_u = cv.entity_id_or_uuid(e_or_u)
if not valid_entity_id(e_or_u):
return e_or_u
if allowed_domain := self.config.get("domain"):
domain = split_entity_id(e_or_u)[0]
if domain != allowed_domain:
raise vol.Invalid(
f"Entity {e_or_u} belongs to domain {domain}, "
f"expected {allowed_domain}"
)
return e_or_u
if not self.config["multiple"]:
return validate(data)
if not isinstance(data, list):
raise vol.Invalid("Value should be a list")
return cast(list, vol.Schema([validate])(data)) # Output is a list
@SELECTORS.register("device")
class DeviceSelector(Selector):
"""Selector of a single device."""
"""Selector of a single or list of devices."""
selector_type = "device"
@@ -124,31 +134,41 @@ class DeviceSelector(Selector):
# Model of device
vol.Optional("model"): str,
# Device has to contain entities matching this selector
vol.Optional("entity"): EntitySelector.CONFIG_SCHEMA,
vol.Optional("entity"): SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA,
vol.Optional("multiple", default=False): cv.boolean,
}
)
def __call__(self, data: Any) -> str:
def __call__(self, data: Any) -> str | list[str]:
"""Validate the passed selection."""
return cv.string(data)
if not self.config["multiple"]:
return cv.string(data)
if not isinstance(data, list):
raise vol.Invalid("Value should be a list")
return [cv.string(val) for val in data]
@SELECTORS.register("area")
class AreaSelector(Selector):
"""Selector of a single area."""
"""Selector of a single or list of areas."""
selector_type = "area"
CONFIG_SCHEMA = vol.Schema(
{
vol.Optional("entity"): EntitySelector.CONFIG_SCHEMA,
vol.Optional("entity"): SINGLE_ENTITY_SELECTOR_CONFIG_SCHEMA,
vol.Optional("device"): DeviceSelector.CONFIG_SCHEMA,
vol.Optional("multiple", default=False): cv.boolean,
}
)
def __call__(self, data: Any) -> str:
def __call__(self, data: Any) -> str | list[str]:
"""Validate the passed selection."""
return cv.string(data)
if not self.config["multiple"]:
return cv.string(data)
if not isinstance(data, list):
raise vol.Invalid("Value should be a list")
return [cv.string(val) for val in data]
@SELECTORS.register("number")