Add support for selectors in services.yaml (#43162)

* Add support for selectors in services.yaml

* Add base schema validation
This commit is contained in:
Paulus Schoutsen 2020-11-13 22:53:55 +01:00 committed by GitHub
parent df25b53bb8
commit bae026a6fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 3 deletions

View file

@ -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,
}
)

View file

@ -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"

View file

@ -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"

View 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,
}
)

View file

@ -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,
}
)

View 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})