Add descriptive fields to script config (#26056)

* Add descriptive fields to script config

* Add script descriptions to hass.data["service_description_cache"]

* Import SERVICE_DESCRIPTION_CACHE

* Register script descriptions via async_set_service_schema

* Add scripts test for loading and reloading service descriptions

* Minor cleanup

* Clean up script schema
This commit is contained in:
Jeff Irion 2019-08-21 14:08:46 -07:00 committed by Paulus Schoutsen
parent 9bcb48985b
commit 7090970436
3 changed files with 128 additions and 1 deletions

View file

@ -20,6 +20,7 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA
from homeassistant.helpers.service import async_set_service_schema
from homeassistant.helpers.script import Script
@ -31,6 +32,9 @@ ATTR_LAST_ACTION = "last_action"
ATTR_LAST_TRIGGERED = "last_triggered"
ATTR_VARIABLES = "variables"
CONF_DESCRIPTION = "description"
CONF_EXAMPLE = "example"
CONF_FIELDS = "fields"
CONF_SEQUENCE = "sequence"
ENTITY_ID_FORMAT = DOMAIN + ".{}"
@ -38,7 +42,17 @@ ENTITY_ID_FORMAT = DOMAIN + ".{}"
GROUP_NAME_ALL_SCRIPTS = "all scripts"
SCRIPT_ENTRY_SCHEMA = vol.Schema(
{CONF_ALIAS: cv.string, vol.Required(CONF_SEQUENCE): cv.SCRIPT_SCHEMA}
{
CONF_ALIAS: cv.string,
vol.Required(CONF_SEQUENCE): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_DESCRIPTION, default=""): cv.string,
vol.Optional(CONF_FIELDS, default={}): {
cv.string: {
vol.Optional(CONF_DESCRIPTION): cv.string,
vol.Optional(CONF_EXAMPLE): cv.string,
}
},
}
)
CONFIG_SCHEMA = vol.Schema(
@ -137,6 +151,13 @@ async def _async_process_config(hass, config, component):
DOMAIN, object_id, service_handler, schema=SCRIPT_SERVICE_SCHEMA
)
# Register the service description
service_desc = {
CONF_DESCRIPTION: cfg[CONF_DESCRIPTION],
CONF_FIELDS: cfg[CONF_FIELDS],
}
async_set_service_schema(hass, DOMAIN, object_id, service_desc)
await component.async_add_entities(scripts)

View file

@ -231,6 +231,20 @@ async def async_get_all_descriptions(hass):
return descriptions
@ha.callback
@bind_hass
def async_set_service_schema(hass, domain, service, schema):
"""Register a description for a service."""
hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {})
description = {
"description": schema.get("description") or "",
"fields": schema.get("fields") or {},
}
hass.data[SERVICE_DESCRIPTION_CACHE]["{}.{}".format(domain, service)] = description
@bind_hass
async def entity_service_call(
hass, platforms, func, call, service_name="", required_features=None

View file

@ -17,6 +17,7 @@ from homeassistant.const import (
EVENT_SCRIPT_STARTED,
)
from homeassistant.core import Context, callback, split_entity_id
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.loader import bind_hass
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.exceptions import ServiceNotFound
@ -244,6 +245,61 @@ class TestScriptComponent(unittest.TestCase):
assert self.hass.services.has_service(script.DOMAIN, "test2")
async def test_service_descriptions(hass):
"""Test that service descriptions are loaded and reloaded correctly."""
# Test 1: has "description" but no "fields"
assert await async_setup_component(
hass,
"script",
{
"script": {
"test": {
"description": "test description",
"sequence": [{"delay": {"seconds": 5}}],
}
}
},
)
descriptions = await async_get_all_descriptions(hass)
assert descriptions[DOMAIN]["test"]["description"] == "test description"
assert not descriptions[DOMAIN]["test"]["fields"]
# Test 2: has "fields" but no "description"
await hass.services.async_call(DOMAIN, SERVICE_RELOAD, blocking=True)
with patch(
"homeassistant.config.load_yaml_config_file",
return_value={
"script": {
"test": {
"fields": {
"test_param": {
"description": "test_param description",
"example": "test_param example",
}
},
"sequence": [{"delay": {"seconds": 5}}],
}
}
},
):
with patch("homeassistant.config.find_config_file", return_value=""):
await hass.services.async_call(DOMAIN, SERVICE_RELOAD, blocking=True)
descriptions = await async_get_all_descriptions(hass)
assert descriptions[script.DOMAIN]["test"]["description"] == ""
assert (
descriptions[script.DOMAIN]["test"]["fields"]["test_param"]["description"]
== "test_param description"
)
assert (
descriptions[script.DOMAIN]["test"]["fields"]["test_param"]["example"]
== "test_param example"
)
async def test_shared_context(hass):
"""Test that the shared context is passed down the chain."""
event = "test_event"
@ -306,3 +362,39 @@ async def test_turning_no_scripts_off(hass):
await hass.services.async_call(
DOMAIN, SERVICE_TURN_OFF, {"entity_id": []}, blocking=True
)
async def test_async_get_descriptions_script(hass):
"""Test async_set_service_schema for the script integration."""
script = hass.components.script
script_config = {
script.DOMAIN: {
"test1": {"sequence": [{"service": "homeassistant.restart"}]},
"test2": {
"description": "test2",
"fields": {
"param": {
"description": "param_description",
"example": "param_example",
}
},
"sequence": [{"service": "homeassistant.restart"}],
},
}
}
await async_setup_component(hass, script.DOMAIN, script_config)
descriptions = await hass.helpers.service.async_get_all_descriptions()
assert descriptions[script.DOMAIN]["test1"]["description"] == ""
assert not descriptions[script.DOMAIN]["test1"]["fields"]
assert descriptions[script.DOMAIN]["test2"]["description"] == "test2"
assert (
descriptions[script.DOMAIN]["test2"]["fields"]["param"]["description"]
== "param_description"
)
assert (
descriptions[script.DOMAIN]["test2"]["fields"]["param"]["example"]
== "param_example"
)