Add support for selectors in services.yaml (#43162)
* Add support for selectors in services.yaml * Add base schema validation
This commit is contained in:
parent
df25b53bb8
commit
bae026a6fe
6 changed files with 144 additions and 3 deletions
|
@ -3,9 +3,9 @@ from typing import Any
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_NAME, CONF_PATH
|
||||
from homeassistant.const import CONF_DOMAIN, CONF_NAME, CONF_PATH, CONF_SELECTOR
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv, selector
|
||||
|
||||
from .const import (
|
||||
CONF_BLUEPRINT,
|
||||
|
@ -32,6 +32,7 @@ BLUEPRINT_INPUT_SCHEMA = vol.Schema(
|
|||
{
|
||||
vol.Optional(CONF_NAME): str,
|
||||
vol.Optional(CONF_DESCRIPTION): str,
|
||||
vol.Optional(CONF_SELECTOR): selector.validate_selector,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -7,6 +7,10 @@ join:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will join the master.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
|
||||
unjoin:
|
||||
description: Unjoin the player from a group.
|
||||
|
@ -14,6 +18,10 @@ unjoin:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will be unjoined from their group.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
|
||||
snapshot:
|
||||
description: Take a snapshot of the media player.
|
||||
|
@ -21,6 +29,10 @@ snapshot:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will be snapshot.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
with_group:
|
||||
description: True (default) or False. Also snapshot the group layout.
|
||||
example: "true"
|
||||
|
@ -31,6 +43,10 @@ restore:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will be restored.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
with_group:
|
||||
description: True (default) or False. Also restore the group layout.
|
||||
example: "true"
|
||||
|
@ -41,6 +57,10 @@ set_sleep_timer:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will have a timer set.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
sleep_time:
|
||||
description: Number of seconds to set the timer.
|
||||
example: "900"
|
||||
|
@ -51,6 +71,10 @@ clear_sleep_timer:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will have the timer cleared.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
|
||||
set_option:
|
||||
description: Set Sonos sound options.
|
||||
|
@ -58,6 +82,10 @@ set_option:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will have options set.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
night_sound:
|
||||
description: Enable Night Sound mode
|
||||
example: "true"
|
||||
|
@ -74,6 +102,10 @@ play_queue:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will start playing.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
queue_position:
|
||||
description: Position of the song in the queue to start playing from.
|
||||
example: "0"
|
||||
|
@ -84,6 +116,10 @@ remove_from_queue:
|
|||
entity_id:
|
||||
description: Name(s) of entities that will remove an item.
|
||||
example: "media_player.living_room_sonos"
|
||||
selector:
|
||||
entity:
|
||||
integration: sonos
|
||||
domain: media_player
|
||||
queue_position:
|
||||
description: Position in the queue to remove.
|
||||
example: "0"
|
||||
|
|
|
@ -154,6 +154,7 @@ CONF_RGB = "rgb"
|
|||
CONF_ROOM = "room"
|
||||
CONF_SCAN_INTERVAL = "scan_interval"
|
||||
CONF_SCENE = "scene"
|
||||
CONF_SELECTOR = "selector"
|
||||
CONF_SENDER = "sender"
|
||||
CONF_SENSORS = "sensors"
|
||||
CONF_SENSOR_TYPE = "sensor_type"
|
||||
|
|
57
homeassistant/helpers/selector.py
Normal file
57
homeassistant/helpers/selector.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
"""Selectors for Home Assistant."""
|
||||
from typing import Any, Callable, Dict, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.util import decorator
|
||||
|
||||
SELECTORS = decorator.Registry()
|
||||
|
||||
|
||||
def validate_selector(config: Any) -> Dict:
|
||||
"""Validate a selector."""
|
||||
if not isinstance(config, dict):
|
||||
raise vol.Invalid("Expected a dictionary")
|
||||
|
||||
if len(config) != 1:
|
||||
raise vol.Invalid(f"Only one type can be specified. Found {', '.join(config)}")
|
||||
|
||||
selector_type = list(config)[0]
|
||||
|
||||
seslector_class = SELECTORS.get(selector_type)
|
||||
|
||||
if seslector_class is None:
|
||||
raise vol.Invalid(f"Unknown selector type {selector_type} found")
|
||||
|
||||
return cast(Dict, seslector_class.CONFIG_SCHEMA(config[selector_type]))
|
||||
|
||||
|
||||
class Selector:
|
||||
"""Base class for selectors."""
|
||||
|
||||
CONFIG_SCHEMA: Callable
|
||||
|
||||
|
||||
@SELECTORS.register("entity")
|
||||
class EntitySelector(Selector):
|
||||
"""Selector of a single entity."""
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional("integration"): str,
|
||||
vol.Optional("domain"): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@SELECTORS.register("device")
|
||||
class DeviceSelector(Selector):
|
||||
"""Selector of a single device."""
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional("integration"): str,
|
||||
vol.Optional("manufacturer"): str,
|
||||
vol.Optional("model"): str,
|
||||
}
|
||||
)
|
|
@ -6,8 +6,9 @@ from typing import Dict
|
|||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from homeassistant.const import CONF_SELECTOR
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import config_validation as cv, selector
|
||||
from homeassistant.util.yaml import load_yaml
|
||||
|
||||
from .model import Integration
|
||||
|
@ -27,6 +28,7 @@ FIELD_SCHEMA = vol.Schema(
|
|||
vol.Optional("default"): exists,
|
||||
vol.Optional("values"): exists,
|
||||
vol.Optional("required"): bool,
|
||||
vol.Optional(CONF_SELECTOR): selector.validate_selector,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
44
tests/helpers/test_selector.py
Normal file
44
tests/helpers/test_selector.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
"""Test selectors."""
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers import selector
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"schema", ({}, {"non_existing": {}}, {"device": {}, "entity": {}})
|
||||
)
|
||||
def test_invalid_base_schema(schema):
|
||||
"""Test base schema validation."""
|
||||
with pytest.raises(vol.Invalid):
|
||||
selector.validate_selector(schema)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"schema",
|
||||
(
|
||||
{},
|
||||
{"integration": "zha"},
|
||||
{"manufacturer": "mock-manuf"},
|
||||
{"model": "mock-model"},
|
||||
{"manufacturer": "mock-manuf", "model": "mock-model"},
|
||||
{"integration": "zha", "manufacturer": "mock-manuf", "model": "mock-model"},
|
||||
),
|
||||
)
|
||||
def test_device_selector_schema(schema):
|
||||
"""Test device selector."""
|
||||
selector.validate_selector({"device": schema})
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"schema",
|
||||
(
|
||||
{},
|
||||
{"integration": "zha"},
|
||||
{"domain": "light"},
|
||||
{"integration": "zha", "domain": "light"},
|
||||
),
|
||||
)
|
||||
def test_entity_selector_schema(schema):
|
||||
"""Test device selector."""
|
||||
selector.validate_selector({"entity": schema})
|
Loading…
Add table
Add a link
Reference in a new issue