From 709097043697794a46377690737db65ff9f2f271 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Wed, 21 Aug 2019 14:08:46 -0700 Subject: [PATCH] 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 --- homeassistant/components/script/__init__.py | 23 +++++- homeassistant/helpers/service.py | 14 ++++ tests/components/script/test_init.py | 92 +++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 44e11d83afa..d810d50cfbf 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -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) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 07e070df8c5..f29d1885d1e 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -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 diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 7be682eff5e..d675034e744 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -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" + )