mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Isolate parallel subscripts (#71233)
This commit is contained in:
parent
1ef060700a
commit
1931600eac
@ -1133,13 +1133,14 @@ class Script:
|
|||||||
domain: str,
|
domain: str,
|
||||||
*,
|
*,
|
||||||
# Used in "Running <running_description>" log message
|
# Used in "Running <running_description>" log message
|
||||||
running_description: str | None = None,
|
|
||||||
change_listener: Callable[..., Any] | None = None,
|
change_listener: Callable[..., Any] | None = None,
|
||||||
script_mode: str = DEFAULT_SCRIPT_MODE,
|
copy_variables: bool = False,
|
||||||
max_runs: int = DEFAULT_MAX,
|
|
||||||
max_exceeded: str = DEFAULT_MAX_EXCEEDED,
|
|
||||||
logger: logging.Logger | None = None,
|
|
||||||
log_exceptions: bool = True,
|
log_exceptions: bool = True,
|
||||||
|
logger: logging.Logger | None = None,
|
||||||
|
max_exceeded: str = DEFAULT_MAX_EXCEEDED,
|
||||||
|
max_runs: int = DEFAULT_MAX,
|
||||||
|
running_description: str | None = None,
|
||||||
|
script_mode: str = DEFAULT_SCRIPT_MODE,
|
||||||
top_level: bool = True,
|
top_level: bool = True,
|
||||||
variables: ScriptVariables | None = None,
|
variables: ScriptVariables | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -1192,6 +1193,7 @@ class Script:
|
|||||||
self._variables_dynamic = template.is_complex(variables)
|
self._variables_dynamic = template.is_complex(variables)
|
||||||
if self._variables_dynamic:
|
if self._variables_dynamic:
|
||||||
template.attach(hass, variables)
|
template.attach(hass, variables)
|
||||||
|
self._copy_variables_on_run = copy_variables
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def change_listener(self) -> Callable[..., Any] | None:
|
def change_listener(self) -> Callable[..., Any] | None:
|
||||||
@ -1454,7 +1456,10 @@ class Script:
|
|||||||
|
|
||||||
variables["context"] = context
|
variables["context"] = context
|
||||||
else:
|
else:
|
||||||
variables = cast(dict, run_variables)
|
if self._copy_variables_on_run:
|
||||||
|
variables = cast(dict, copy(run_variables))
|
||||||
|
else:
|
||||||
|
variables = cast(dict, run_variables)
|
||||||
|
|
||||||
# Prevent non-allowed recursive calls which will cause deadlocks when we try to
|
# Prevent non-allowed recursive calls which will cause deadlocks when we try to
|
||||||
# stop (restart) or wait for (queued) our own script run.
|
# stop (restart) or wait for (queued) our own script run.
|
||||||
@ -1671,6 +1676,7 @@ class Script:
|
|||||||
max_runs=self.max_runs,
|
max_runs=self.max_runs,
|
||||||
logger=self._logger,
|
logger=self._logger,
|
||||||
top_level=False,
|
top_level=False,
|
||||||
|
copy_variables=True,
|
||||||
)
|
)
|
||||||
parallel_script.change_listener = partial(
|
parallel_script.change_listener = partial(
|
||||||
self._chain_change_listener, parallel_script
|
self._chain_change_listener, parallel_script
|
||||||
|
@ -3027,7 +3027,7 @@ async def test_parallel(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -
|
|||||||
],
|
],
|
||||||
"0/parallel/1/sequence/0": [
|
"0/parallel/1/sequence/0": [
|
||||||
{
|
{
|
||||||
"variables": {"wait": {"remaining": None}},
|
"variables": {},
|
||||||
"result": {
|
"result": {
|
||||||
"event": "test_event",
|
"event": "test_event",
|
||||||
"event_data": {"hello": "from action 2", "what": "world"},
|
"event_data": {"hello": "from action 2", "what": "world"},
|
||||||
@ -3047,6 +3047,150 @@ async def test_parallel(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -
|
|||||||
assert_action_trace(expected_trace)
|
assert_action_trace(expected_trace)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_parallel_loop(
|
||||||
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
|
) -> None:
|
||||||
|
"""Test parallel loops do not affect each other."""
|
||||||
|
events_loop1 = async_capture_events(hass, "loop1")
|
||||||
|
events_loop2 = async_capture_events(hass, "loop2")
|
||||||
|
hass.states.async_set("switch.trigger", "off")
|
||||||
|
|
||||||
|
sequence = cv.SCRIPT_SCHEMA(
|
||||||
|
{
|
||||||
|
"parallel": [
|
||||||
|
{
|
||||||
|
"alias": "Loop1",
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"repeat": {
|
||||||
|
"for_each": ["loop1_a", "loop1_b", "loop1_c"],
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"event": "loop1",
|
||||||
|
"event_data": {"hello1": "{{ repeat.item }}"},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": "Loop2",
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"repeat": {
|
||||||
|
"for_each": ["loop2_a", "loop2_b", "loop2_c"],
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"event": "loop2",
|
||||||
|
"event_data": {"hello2": "{{ repeat.item }}"},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
script_obj.async_run(MappingProxyType({"what": "world"}), Context())
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(events_loop1) == 3
|
||||||
|
assert events_loop1[0].data["hello1"] == "loop1_a"
|
||||||
|
assert events_loop1[1].data["hello1"] == "loop1_b"
|
||||||
|
assert events_loop1[2].data["hello1"] == "loop1_c"
|
||||||
|
assert events_loop2[0].data["hello2"] == "loop2_a"
|
||||||
|
assert events_loop2[1].data["hello2"] == "loop2_b"
|
||||||
|
assert events_loop2[2].data["hello2"] == "loop2_c"
|
||||||
|
|
||||||
|
expected_trace = {
|
||||||
|
"0": [{"result": {}}],
|
||||||
|
"0/parallel/0/sequence/0": [{"result": {}}],
|
||||||
|
"0/parallel/1/sequence/0": [
|
||||||
|
{
|
||||||
|
"result": {},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/parallel/0/sequence/0/repeat/sequence/0": [
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"repeat": {
|
||||||
|
"first": True,
|
||||||
|
"index": 1,
|
||||||
|
"last": False,
|
||||||
|
"item": "loop1_a",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {"event": "loop1", "event_data": {"hello1": "loop1_a"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"repeat": {
|
||||||
|
"first": False,
|
||||||
|
"index": 2,
|
||||||
|
"last": False,
|
||||||
|
"item": "loop1_b",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {"event": "loop1", "event_data": {"hello1": "loop1_b"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"repeat": {
|
||||||
|
"first": False,
|
||||||
|
"index": 3,
|
||||||
|
"last": True,
|
||||||
|
"item": "loop1_c",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {"event": "loop1", "event_data": {"hello1": "loop1_c"}},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"0/parallel/1/sequence/0/repeat/sequence/0": [
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"repeat": {
|
||||||
|
"first": True,
|
||||||
|
"index": 1,
|
||||||
|
"last": False,
|
||||||
|
"item": "loop2_a",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {"event": "loop2", "event_data": {"hello2": "loop2_a"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"repeat": {
|
||||||
|
"first": False,
|
||||||
|
"index": 2,
|
||||||
|
"last": False,
|
||||||
|
"item": "loop2_b",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {"event": "loop2", "event_data": {"hello2": "loop2_b"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"variables": {
|
||||||
|
"repeat": {
|
||||||
|
"first": False,
|
||||||
|
"index": 3,
|
||||||
|
"last": True,
|
||||||
|
"item": "loop2_c",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {"event": "loop2", "event_data": {"hello2": "loop2_c"}},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
assert_action_trace(expected_trace)
|
||||||
|
|
||||||
|
|
||||||
async def test_parallel_error(
|
async def test_parallel_error(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user