mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
Allow returning a script variable from a script (#95346)
* Allow returning a script variable from a script * Don't allow returning a template result * Raise if response variable is undefined * Add test * Update homeassistant/helpers/script.py Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Format code --------- Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
e19b29d6ae
commit
cb22fb16f8
@ -224,7 +224,6 @@ CONF_RESOURCE: Final = "resource"
|
|||||||
CONF_RESOURCE_TEMPLATE: Final = "resource_template"
|
CONF_RESOURCE_TEMPLATE: Final = "resource_template"
|
||||||
CONF_RESOURCES: Final = "resources"
|
CONF_RESOURCES: Final = "resources"
|
||||||
CONF_RESPONSE_VARIABLE: Final = "response_variable"
|
CONF_RESPONSE_VARIABLE: Final = "response_variable"
|
||||||
CONF_RESPONSE: Final = "response"
|
|
||||||
CONF_RGB: Final = "rgb"
|
CONF_RGB: Final = "rgb"
|
||||||
CONF_ROOM: Final = "room"
|
CONF_ROOM: Final = "room"
|
||||||
CONF_SCAN_INTERVAL: Final = "scan_interval"
|
CONF_SCAN_INTERVAL: Final = "scan_interval"
|
||||||
|
@ -59,7 +59,6 @@ from homeassistant.const import (
|
|||||||
CONF_PARALLEL,
|
CONF_PARALLEL,
|
||||||
CONF_PLATFORM,
|
CONF_PLATFORM,
|
||||||
CONF_REPEAT,
|
CONF_REPEAT,
|
||||||
CONF_RESPONSE,
|
|
||||||
CONF_RESPONSE_VARIABLE,
|
CONF_RESPONSE_VARIABLE,
|
||||||
CONF_SCAN_INTERVAL,
|
CONF_SCAN_INTERVAL,
|
||||||
CONF_SCENE,
|
CONF_SCENE,
|
||||||
@ -1691,10 +1690,7 @@ _SCRIPT_STOP_SCHEMA = vol.Schema(
|
|||||||
**SCRIPT_ACTION_BASE_SCHEMA,
|
**SCRIPT_ACTION_BASE_SCHEMA,
|
||||||
vol.Required(CONF_STOP): vol.Any(None, string),
|
vol.Required(CONF_STOP): vol.Any(None, string),
|
||||||
vol.Exclusive(CONF_ERROR, "error_or_response"): boolean,
|
vol.Exclusive(CONF_ERROR, "error_or_response"): boolean,
|
||||||
vol.Exclusive(CONF_RESPONSE, "error_or_response"): vol.Any(
|
vol.Exclusive(CONF_RESPONSE_VARIABLE, "error_or_response"): str,
|
||||||
vol.All(dict, template_complex),
|
|
||||||
vol.All(str, template),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,7 +46,6 @@ from homeassistant.const import (
|
|||||||
CONF_MODE,
|
CONF_MODE,
|
||||||
CONF_PARALLEL,
|
CONF_PARALLEL,
|
||||||
CONF_REPEAT,
|
CONF_REPEAT,
|
||||||
CONF_RESPONSE,
|
|
||||||
CONF_RESPONSE_VARIABLE,
|
CONF_RESPONSE_VARIABLE,
|
||||||
CONF_SCENE,
|
CONF_SCENE,
|
||||||
CONF_SEQUENCE,
|
CONF_SEQUENCE,
|
||||||
@ -1031,10 +1030,14 @@ class _ScriptRun:
|
|||||||
raise _AbortScript(stop)
|
raise _AbortScript(stop)
|
||||||
|
|
||||||
self._log("Stop script sequence: %s", stop)
|
self._log("Stop script sequence: %s", stop)
|
||||||
if CONF_RESPONSE in self._action:
|
if CONF_RESPONSE_VARIABLE in self._action:
|
||||||
response = template.render_complex(
|
try:
|
||||||
self._action[CONF_RESPONSE], self._variables
|
response = self._variables[self._action[CONF_RESPONSE_VARIABLE]]
|
||||||
)
|
except KeyError as ex:
|
||||||
|
raise _AbortScript(
|
||||||
|
f"Response variable '{self._action[CONF_RESPONSE_VARIABLE]}' "
|
||||||
|
"is not defined"
|
||||||
|
) from ex
|
||||||
else:
|
else:
|
||||||
response = None
|
response = None
|
||||||
raise _StopScript(stop, response)
|
raise _StopScript(stop, response)
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.core import (
|
|||||||
callback,
|
callback,
|
||||||
split_entity_id,
|
split_entity_id,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import ServiceNotFound
|
from homeassistant.exceptions import HomeAssistantError, ServiceNotFound
|
||||||
from homeassistant.helpers import entity_registry as er, template
|
from homeassistant.helpers import entity_registry as er, template
|
||||||
from homeassistant.helpers.event import async_track_state_change
|
from homeassistant.helpers.event import async_track_state_change
|
||||||
from homeassistant.helpers.script import (
|
from homeassistant.helpers.script import (
|
||||||
@ -1515,10 +1515,15 @@ async def test_responses(hass: HomeAssistant, response: Any) -> None:
|
|||||||
{
|
{
|
||||||
"script": {
|
"script": {
|
||||||
"test": {
|
"test": {
|
||||||
"sequence": {
|
"sequence": [
|
||||||
"stop": "done",
|
{
|
||||||
"response": response,
|
"variables": {"test_var": {"response": response}},
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"stop": "done",
|
||||||
|
"response_variable": "test_var",
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1526,7 +1531,40 @@ async def test_responses(hass: HomeAssistant, response: Any) -> None:
|
|||||||
|
|
||||||
assert await hass.services.async_call(
|
assert await hass.services.async_call(
|
||||||
DOMAIN, "test", {"greeting": "world"}, blocking=True, return_response=True
|
DOMAIN, "test", {"greeting": "world"}, blocking=True, return_response=True
|
||||||
) == {"value": 5}
|
) == {"response": response}
|
||||||
|
# Validate we can also call it without return_response
|
||||||
|
assert (
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN, "test", {"greeting": "world"}, blocking=True, return_response=False
|
||||||
|
)
|
||||||
|
is None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_responses_error(hass: HomeAssistant) -> None:
|
||||||
|
"""Test response variable not set."""
|
||||||
|
mock_restore_cache(hass, ())
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"script",
|
||||||
|
{
|
||||||
|
"script": {
|
||||||
|
"test": {
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"stop": "done",
|
||||||
|
"response_variable": "test_var",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
DOMAIN, "test", {"greeting": "world"}, blocking=True, return_response=True
|
||||||
|
)
|
||||||
# Validate we can also call it without return_response
|
# Validate we can also call it without return_response
|
||||||
assert (
|
assert (
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
|
@ -24,7 +24,10 @@ from homeassistant.setup import DATA_SETUP_TIME, async_setup_component
|
|||||||
from homeassistant.util.json import json_loads
|
from homeassistant.util.json import json_loads
|
||||||
|
|
||||||
from tests.common import MockEntity, MockEntityPlatform, MockUser, async_mock_service
|
from tests.common import MockEntity, MockEntityPlatform, MockUser, async_mock_service
|
||||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
from tests.typing import (
|
||||||
|
ClientSessionGenerator,
|
||||||
|
WebSocketGenerator,
|
||||||
|
)
|
||||||
|
|
||||||
STATE_KEY_SHORT_NAMES = {
|
STATE_KEY_SHORT_NAMES = {
|
||||||
"entity_id": "e",
|
"entity_id": "e",
|
||||||
@ -1686,7 +1689,7 @@ async def test_execute_script(hass: HomeAssistant, websocket_client) -> None:
|
|||||||
"data": {"hello": "world"},
|
"data": {"hello": "world"},
|
||||||
"response_variable": "service_result",
|
"response_variable": "service_result",
|
||||||
},
|
},
|
||||||
{"stop": "done", "response": "{{ service_result }}"},
|
{"stop": "done", "response_variable": "service_result"},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1732,6 +1735,48 @@ async def test_execute_script(hass: HomeAssistant, websocket_client) -> None:
|
|||||||
assert call.context.as_dict() == msg_var["result"]["context"]
|
assert call.context.as_dict() == msg_var["result"]["context"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_execute_script_complex_response(
|
||||||
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
|
) -> None:
|
||||||
|
"""Test testing a condition."""
|
||||||
|
await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
ws_client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
await ws_client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "execute_script",
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"service": "calendar.list_events",
|
||||||
|
"data": {"duration": {"hours": 24, "minutes": 0, "seconds": 0}},
|
||||||
|
"target": {"entity_id": "calendar.calendar_1"},
|
||||||
|
"response_variable": "service_result",
|
||||||
|
},
|
||||||
|
{"stop": "done", "response_variable": "service_result"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
msg_no_var = await ws_client.receive_json()
|
||||||
|
assert msg_no_var["type"] == const.TYPE_RESULT
|
||||||
|
assert msg_no_var["success"]
|
||||||
|
assert msg_no_var["result"]["response"] == {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"start": ANY,
|
||||||
|
"end": ANY,
|
||||||
|
"summary": "Future Event",
|
||||||
|
"description": "Future Description",
|
||||||
|
"location": "Future Location",
|
||||||
|
"uid": None,
|
||||||
|
"recurrence_id": None,
|
||||||
|
"rrule": None,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_subscribe_unsubscribe_bootstrap_integrations(
|
async def test_subscribe_unsubscribe_bootstrap_integrations(
|
||||||
hass: HomeAssistant, websocket_client, hass_admin_user: MockUser
|
hass: HomeAssistant, websocket_client, hass_admin_user: MockUser
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user