1 """Helpers to execute scripts."""
3 from __future__
import annotations
6 from collections.abc
import AsyncGenerator, Callable, Mapping, Sequence
7 from contextlib
import asynccontextmanager
8 from contextvars
import ContextVar
10 from dataclasses
import dataclass
11 from datetime
import datetime, timedelta
12 from functools
import partial
15 from types
import MappingProxyType
16 from typing
import Any, Literal, TypedDict, cast, overload
18 import async_interrupt
19 from propcache
import cached_property
20 import voluptuous
as vol
22 from homeassistant
import exceptions
36 CONF_CONTINUE_ON_ERROR,
37 CONF_CONTINUE_ON_TIMEOUT,
48 CONF_EVENT_DATA_TEMPLATE,
54 CONF_RESPONSE_VARIABLE,
59 CONF_SERVICE_DATA_TEMPLATE,
60 CONF_SET_CONVERSATION_RESPONSE,
67 CONF_WAIT_FOR_TRIGGER,
70 EVENT_HOMEASSISTANT_STOP,
89 from .
import condition, config_validation
as cv, service, template
90 from .condition
import ConditionCheckerType, trace_condition_function
91 from .dispatcher
import async_dispatcher_connect, async_dispatcher_send_internal
92 from .event
import async_call_later, async_track_template
93 from .script_variables
import ScriptVariables
94 from .template
import Template
111 from .trigger
import async_initialize_triggers, async_validate_trigger_config
112 from .typing
import UNDEFINED, ConfigType, TemplateVarsType, UndefinedType
114 SCRIPT_MODE_PARALLEL =
"parallel"
115 SCRIPT_MODE_QUEUED =
"queued"
116 SCRIPT_MODE_RESTART =
"restart"
117 SCRIPT_MODE_SINGLE =
"single"
118 SCRIPT_MODE_CHOICES = [
119 SCRIPT_MODE_PARALLEL,
124 DEFAULT_SCRIPT_MODE = SCRIPT_MODE_SINGLE
129 CONF_MAX_EXCEEDED =
"max_exceeded"
130 _MAX_EXCEEDED_CHOICES = [*LOGSEVERITY,
"SILENT"]
131 DEFAULT_MAX_EXCEEDED =
"WARNING"
136 DATA_SCRIPTS: HassKey[list[ScriptData]] =
HassKey(
"helpers.script")
137 DATA_SCRIPT_BREAKPOINTS: HassKey[dict[str, dict[str, set[str]]]] =
HassKey(
138 "helpers.script_breakpoints"
140 DATA_NEW_SCRIPT_RUNS_NOT_ALLOWED: HassKey[
None] =
HassKey(
"helpers.script_not_allowed")
144 _LOGGER = logging.getLogger(__name__)
146 _LOG_EXCEPTION = logging.ERROR + 1
147 _TIMEOUT_MSG =
"Timeout reached, abort script."
149 _SHUTDOWN_MAX_WAIT = 60
152 ACTION_TRACE_NODE_MAX_LEN = 20
154 SCRIPT_BREAKPOINT_HIT = SignalType[str, str, str](
"script_breakpoint_hit")
155 SCRIPT_DEBUG_CONTINUE_STOP: SignalTypeFormat[Literal[
"continue",
"stop"]] = (
158 SCRIPT_DEBUG_CONTINUE_ALL =
"script_debug_continue_all"
160 script_stack_cv: ContextVar[list[str] |
None] = ContextVar(
"script_stack", default=
None)
164 """Store data related to script instance."""
167 started_before_shutdown: bool
171 """Error to indicate that the script has been stopped."""
175 """Set result of future unless it is done."""
176 if not future.done():
177 future.set_result(
None)
181 """Append a TraceElement to trace[path]."""
190 script_run: _ScriptRun,
191 stop: asyncio.Future[
None],
192 variables: dict[str, Any],
193 ) -> AsyncGenerator[TraceElement]:
194 """Trace action execution."""
197 trace_stack_push(trace_stack_cv, trace_element)
203 breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS]
204 if key
in breakpoints
and (
206 run_id
in breakpoints[key]
208 path
in breakpoints[key][run_id]
209 or NODE_ANY
in breakpoints[key][run_id]
213 RUN_ID_ANY
in breakpoints[key]
215 path
in breakpoints[key][RUN_ID_ANY]
216 or NODE_ANY
in breakpoints[key][RUN_ID_ANY]
220 async_dispatcher_send_internal(
221 hass, SCRIPT_BREAKPOINT_HIT, key, run_id, path
224 done = hass.loop.create_future()
227 def async_continue_stop(
228 command: Literal[
"continue",
"stop"] |
None =
None,
230 if command ==
"stop":
234 signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id)
237 hass, SCRIPT_DEBUG_CONTINUE_ALL, async_continue_stop
240 await asyncio.wait([stop, done], return_when=asyncio.FIRST_COMPLETED)
246 except _AbortScript
as ex:
247 trace_element.set_error(ex.__cause__
or ex)
249 except _ConditionFail:
251 trace_element.set_error(
None)
255 except Exception
as ex:
256 trace_element.set_error(ex)
263 schema: Mapping[Any, Any], default_script_mode: str, extra: int = vol.PREVENT_EXTRA
265 """Make a schema for a component that uses the script helper."""
269 vol.Optional(CONF_MODE, default=default_script_mode): vol.In(
272 vol.Optional(CONF_MAX, default=DEFAULT_MAX): vol.All(
273 vol.Coerce(int), vol.Range(min=2)
275 vol.Optional(CONF_MAX_EXCEEDED, default=DEFAULT_MAX_EXCEEDED): vol.All(
276 vol.Upper, vol.In(_MAX_EXCEEDED_CHOICES)
283 STATIC_VALIDATION_ACTION_TYPES = (
284 cv.SCRIPT_ACTION_ACTIVATE_SCENE,
285 cv.SCRIPT_ACTION_CALL_SERVICE,
286 cv.SCRIPT_ACTION_DELAY,
287 cv.SCRIPT_ACTION_FIRE_EVENT,
288 cv.SCRIPT_ACTION_SET_CONVERSATION_RESPONSE,
289 cv.SCRIPT_ACTION_STOP,
290 cv.SCRIPT_ACTION_VARIABLES,
291 cv.SCRIPT_ACTION_WAIT_TEMPLATE,
294 REPEAT_WARN_ITERATIONS = 5000
295 REPEAT_TERMINATE_ITERATIONS = 10000
299 hass: HomeAssistant, actions: list[ConfigType]
300 ) -> list[ConfigType]:
301 """Validate a list of actions."""
308 hass: HomeAssistant, config: ConfigType
310 """Validate config."""
311 action_type = cv.determine_script_action(config)
313 if action_type
in STATIC_VALIDATION_ACTION_TYPES:
316 elif action_type == cv.SCRIPT_ACTION_DEVICE_AUTOMATION:
317 config = await device_action.async_validate_action_config(hass, config)
319 elif action_type == cv.SCRIPT_ACTION_CHECK_CONDITION:
320 config = await condition.async_validate_condition_config(hass, config)
322 elif action_type == cv.SCRIPT_ACTION_WAIT_FOR_TRIGGER:
324 hass, config[CONF_WAIT_FOR_TRIGGER]
327 elif action_type == cv.SCRIPT_ACTION_REPEAT:
328 if CONF_UNTIL
in config[CONF_REPEAT]:
329 conditions = await condition.async_validate_conditions_config(
330 hass, config[CONF_REPEAT][CONF_UNTIL]
332 config[CONF_REPEAT][CONF_UNTIL] = conditions
333 if CONF_WHILE
in config[CONF_REPEAT]:
334 conditions = await condition.async_validate_conditions_config(
335 hass, config[CONF_REPEAT][CONF_WHILE]
337 config[CONF_REPEAT][CONF_WHILE] = conditions
339 hass, config[CONF_REPEAT][CONF_SEQUENCE]
342 elif action_type == cv.SCRIPT_ACTION_CHOOSE:
343 if CONF_DEFAULT
in config:
345 hass, config[CONF_DEFAULT]
348 for choose_conf
in config[CONF_CHOOSE]:
349 conditions = await condition.async_validate_conditions_config(
350 hass, choose_conf[CONF_CONDITIONS]
352 choose_conf[CONF_CONDITIONS] = conditions
354 hass, choose_conf[CONF_SEQUENCE]
357 elif action_type == cv.SCRIPT_ACTION_IF:
358 config[CONF_IF] = await condition.async_validate_conditions_config(
359 hass, config[CONF_IF]
362 if CONF_ELSE
in config:
364 hass, config[CONF_ELSE]
367 elif action_type == cv.SCRIPT_ACTION_PARALLEL:
368 for parallel_conf
in config[CONF_PARALLEL]:
370 hass, parallel_conf[CONF_SEQUENCE]
373 elif action_type == cv.SCRIPT_ACTION_SEQUENCE:
375 hass, config[CONF_SEQUENCE]
379 raise ValueError(f
"No validation for {action_type}")
385 """Throw if script needs to stop executing."""
389 """Throw if script needs to abort because of an unexpected error."""
393 """Throw if script needs to stop because a condition evaluated to False."""
397 """Throw if script needs to stop."""
399 def __init__(self, message: str, response: Any) ->
None:
400 """Initialize a halt exception."""
406 """Manage Script sequence run."""
408 _action: dict[str, Any]
414 variables: dict[str, Any],
415 context: Context |
None,
416 log_exceptions: bool,
425 self.
_stop_stop = hass.loop.create_future()
430 if not self.
_stop_stop.done():
437 self, msg: str, *args: Any, level: int = logging.INFO, **kwargs: Any
439 self.
_script_script.
_log(msg, *args, level=level, **kwargs)
441 def _step_log(self, default_message: str, timeout: float |
None =
None) ->
None:
442 self.
_script_script.last_action = self._action.
get(CONF_ALIAS, default_message)
444 "" if timeout
is None else f
" (timeout: {timedelta(seconds=timeout)})"
446 self.
_log_log(
"Executing step %s%s", self.
_script_script.last_action, _timeout)
452 if (script_stack := script_stack_cv.get())
is None:
454 script_stack_cv.set(script_stack)
455 script_stack.append(self.
_script_script.unique_id)
459 self.
_log_log(
"Running %s", self.
_script_script.running_description)
460 for self.
_step_step, self._action
in enumerate(self.
_script_script.sequence):
461 if self.
_stop_stop.done():
464 await self.
_async_step_async_step(log_exceptions=
False)
470 if not self.
_script_script.top_level:
472 except _ConditionFail:
474 except _StopScript
as err:
478 if not self.
_script_script.top_level:
481 response = err.response
494 continue_on_error = self._action.
get(CONF_CONTINUE_ON_ERROR,
False)
500 if self.
_stop_stop.done():
503 action = cv.determine_script_action(self._action)
505 if CONF_ENABLED
in self._action:
506 enabled = self._action[CONF_ENABLED]
507 if isinstance(enabled, Template):
509 enabled = enabled.async_render(limited=
True)
518 "Skipped disabled step %s",
519 self._action.
get(CONF_ALIAS, action),
524 handler = f
"_async_{action}_step"
526 await getattr(self, handler)()
527 except Exception
as ex:
529 ex, continue_on_error, self.
_log_exceptions_log_exceptions
or log_exceptions
532 trace_element.update_variables(self.
_variables_variables)
535 self.
_script_script._runs.remove(self)
536 if not self.
_script_script.is_running:
537 self.
_script_script.last_action =
None
542 """Stop script run."""
552 self, exception: Exception, continue_on_error: bool, log_exceptions: bool
554 if not isinstance(exception, _HaltScript)
and log_exceptions:
557 if not continue_on_error:
561 if isinstance(exception, _StopScript):
584 action_type = cv.determine_script_action(self._action)
586 error =
str(exception)
587 level = logging.ERROR
589 if isinstance(exception, vol.Invalid):
590 error_desc =
"Invalid data"
593 error_desc =
"Error rendering template"
596 error_desc =
"Unauthorized"
599 error_desc =
"Service not found"
605 error_desc =
"Unexpected error"
606 level = _LOG_EXCEPTION
609 "Error executing script. %s for %s at pos %s: %s",
619 return cv.positive_time_period(
620 template.render_complex(self._action[key], self.
_variables_variables)
624 "Error rendering %s %s template: %s",
630 raise _AbortScript
from ex
636 self.
_step_log_step_log(f
"delay {delay_delta}")
638 delay = delay_delta.total_seconds()
651 await asyncio.wait(futures, return_when=asyncio.FIRST_COMPLETED)
653 if timeout_future.done():
656 timeout_handle.cancel()
659 """Get the timeout from the action."""
660 if CONF_TIMEOUT
in self._action:
665 """Handle a wait template."""
667 self.
_step_log_step_log(
"wait template", timeout)
669 self.
_variables_variables[
"wait"] = {
"remaining": timeout,
"completed":
False}
672 wait_template = self._action[CONF_WAIT_TEMPLATE]
675 if condition.async_template(self.
_hass_hass, wait_template, self.
_variables_variables,
False):
676 self.
_variables_variables[
"wait"][
"completed"] =
True
688 done = self.
_hass_hass.loop.create_future()
692 def async_script_wait(
693 entity_id: str, from_s: State |
None, to_s: State |
None
695 """Handle script after template condition is true."""
697 self.
_variables_variables[
"wait"][
"completed"] =
True
701 self.
_hass_hass, wait_template, async_script_wait, self.
_variables_variables
705 futures, timeout_handle, timeout_future, unsub
709 self, timeout_handle: asyncio.TimerHandle |
None
711 """Set the remaining time variable for a wait step."""
714 wait_var[
"remaining"] = timeout_handle.when() - self.
_hass_hass.loop.time()
716 wait_var[
"remaining"] =
None
718 async
def _async_run_long_action[_T](
719 self, long_task: asyncio.Task[_T]
721 """Run a long task while monitoring for stop request."""
723 async
with async_interrupt.interrupt(self.
_stop_stop, ScriptStoppedError,
None):
727 return await long_task
728 except ScriptStoppedError
as ex:
729 raise asyncio.CancelledError
from ex
732 """Call the service specified in the action."""
735 params = service.async_prepare_call_from_config(
741 response_variable = self._action.
get(CONF_RESPONSE_VARIABLE)
742 return_response = response_variable
is not None
743 if self.
_hass_hass.services.has_service(params[CONF_DOMAIN], params[CONF_SERVICE]):
744 supports_response = self.
_hass_hass.services.supports_response(
745 params[CONF_DOMAIN], params[CONF_SERVICE]
747 if supports_response == SupportsResponse.ONLY
and not return_response:
749 f
"Script requires '{CONF_RESPONSE_VARIABLE}' for response data "
750 f
"for service call {params[CONF_DOMAIN]}.{params[CONF_SERVICE]}"
752 if supports_response == SupportsResponse.NONE
and return_response:
754 f
"Script does not support '{CONF_RESPONSE_VARIABLE}' for service "
755 f
"'{CONF_RESPONSE_VARIABLE}' which does not support response data."
759 params[CONF_DOMAIN] ==
"automation"
760 and params[CONF_SERVICE] ==
"trigger"
761 or params[CONF_DOMAIN]
in (
"python_script",
"script")
764 response_data = await self._async_run_long_action(
765 self.
_hass_hass.async_create_task_internal(
766 self.
_hass_hass.services.async_call(
770 return_response=return_response,
775 if response_variable:
776 self.
_variables_variables[response_variable] = response_data
779 """Perform the device automation specified in the action."""
780 self.
_step_log_step_log(
"device automation")
781 await device_action.async_call_action_from_config(
786 """Activate the scene specified in the action."""
787 self.
_step_log_step_log(
"activate scene")
789 await self.
_hass_hass.services.async_call(
792 {ATTR_ENTITY_ID: self._action[CONF_SCENE]},
799 self.
_step_log_step_log(self._action.
get(CONF_ALIAS, self._action[CONF_EVENT]))
801 for conf
in (CONF_EVENT_DATA, CONF_EVENT_DATA_TEMPLATE):
802 if conf
not in self._action:
807 template.render_complex(self._action[conf], self.
_variables_variables)
811 "Error rendering event data template: %s", ex, level=logging.ERROR
815 self.
_hass_hass.bus.async_fire_internal(
816 self._action[CONF_EVENT], event_data, context=self.
_context_context
820 """Test if condition is matching."""
821 self.
_script_script.last_action = self._action.
get(
822 CONF_ALIAS, self._action[CONF_CONDITION]
826 trace_element = trace_stack_top(trace_stack_cv)
828 trace_element.reuse_by_child =
True
831 _LOGGER.warning(
"Error in 'condition' evaluation:\n%s", ex)
834 self.
_log_log(
"Test condition %s: %s", self.
_script_script.last_action, check)
841 conditions: list[ConditionCheckerType],
843 condition_path: str |
None =
None,
845 if condition_path
is None:
846 condition_path = name
848 @trace_condition_function
849 def traced_test_conditions(
850 hass: HomeAssistant, variables: TemplateVarsType
854 for idx, cond
in enumerate(conditions):
856 if cond(hass, variables)
is False:
859 _LOGGER.warning(
"Error in '%s[%s]' evaluation: %s", name, idx, ex)
864 return traced_test_conditions(self.
_hass_hass, self.
_variables_variables)
866 @async_trace_path("repeat")
868 """Repeat a sequence."""
869 description = self._action.
get(CONF_ALIAS,
"sequence")
870 repeat = self._action[CONF_REPEAT]
875 iteration: int, count: int |
None =
None, item: Any =
None
877 repeat_vars = {
"first": iteration == 1,
"index": iteration}
879 repeat_vars[
"last"] = iteration == count
881 repeat_vars[
"item"] = item
882 self.
_variables_variables[
"repeat"] = repeat_vars
884 script = self.
_script_script._get_repeat_script(self.
_step_step)
885 warned_too_many_loops =
False
887 async
def async_run_sequence(iteration: int, extra_msg: str =
"") ->
None:
888 self.
_log_log(
"Repeating %s: Iteration %i%s", description, iteration, extra_msg)
892 if CONF_COUNT
in repeat:
893 count = repeat[CONF_COUNT]
899 "Error rendering %s repeat count template: %s",
904 raise _AbortScript
from ex
905 extra_msg = f
" of {count}"
906 for iteration
in range(1, count + 1):
907 set_repeat_var(iteration, count)
908 await async_run_sequence(iteration, extra_msg)
909 if self.
_stop_stop.done():
912 elif CONF_FOR_EACH
in repeat:
914 items = template.render_complex(repeat[CONF_FOR_EACH], self.
_variables_variables)
917 "Error rendering %s repeat for each items template: %s",
922 raise _AbortScript
from ex
924 if not isinstance(items, list):
926 "Repeat 'for_each' must be a list of items in %s, got: %s",
931 raise _AbortScript(
"Repeat 'for_each' must be a list of items")
934 for iteration, item
in enumerate(items, 1):
935 set_repeat_var(iteration, count, item)
936 extra_msg = f
" of {count} with item: {item!r}"
937 if self.
_stop_stop.done():
939 await async_run_sequence(iteration, extra_msg)
941 elif CONF_WHILE
in repeat:
945 for iteration
in itertools.count(1):
946 set_repeat_var(iteration)
948 if self.
_stop_stop.done():
953 _LOGGER.warning(
"Error in 'while' evaluation:\n%s", ex)
957 if iteration > REPEAT_WARN_ITERATIONS:
958 if not warned_too_many_loops:
959 warned_too_many_loops =
True
961 "While condition %s in script `%s` looped %s times",
964 REPEAT_WARN_ITERATIONS,
967 if iteration > REPEAT_TERMINATE_ITERATIONS:
969 "While condition %s in script `%s` "
970 "terminated because it looped %s times",
973 REPEAT_TERMINATE_ITERATIONS,
976 f
"While condition {repeat[CONF_WHILE]} "
977 "terminated because it looped "
978 f
" {REPEAT_TERMINATE_ITERATIONS} times"
984 await asyncio.sleep(0)
986 await async_run_sequence(iteration)
988 elif CONF_UNTIL
in repeat:
992 for iteration
in itertools.count(1):
993 set_repeat_var(iteration)
994 await async_run_sequence(iteration)
996 if self.
_stop_stop.done():
998 if self.
_test_conditions_test_conditions(conditions,
"until")
in [
True,
None]:
1001 _LOGGER.warning(
"Error in 'until' evaluation:\n%s", ex)
1004 if iteration >= REPEAT_WARN_ITERATIONS:
1005 if not warned_too_many_loops:
1006 warned_too_many_loops =
True
1008 "Until condition %s in script `%s` looped %s times",
1011 REPEAT_WARN_ITERATIONS,
1014 if iteration >= REPEAT_TERMINATE_ITERATIONS:
1016 "Until condition %s in script `%s` "
1017 "terminated because it looped %s times",
1020 REPEAT_TERMINATE_ITERATIONS,
1023 f
"Until condition {repeat[CONF_UNTIL]} "
1024 "terminated because it looped "
1025 f
"{REPEAT_TERMINATE_ITERATIONS} times"
1031 await asyncio.sleep(0)
1033 if saved_repeat_vars:
1034 self.
_variables_variables[
"repeat"] = saved_repeat_vars
1036 self.
_variables_variables.pop(
"repeat",
None)
1039 """Choose a sequence."""
1040 choose_data = await self.
_script_script._async_get_choose_data(self.
_step_step)
1043 for idx, (conditions, script)
in enumerate(choose_data[
"choices"]):
1046 if self.
_test_conditions_test_conditions(conditions,
"choose",
"conditions"):
1052 _LOGGER.warning(
"Error in 'choose' evaluation:\n%s", ex)
1054 if choose_data[
"default"]
is not None:
1061 if_data = await self.
_script_script._async_get_if_data(self.
_step_step)
1063 test_conditions: bool |
None =
False
1067 if_data[
"if_conditions"],
"if",
"condition"
1070 _LOGGER.warning(
"Error in 'if' evaluation:\n%s", ex)
1078 if if_data[
"if_else"]
is not None:
1088 list[asyncio.Future[
None]],
1089 asyncio.TimerHandle,
1090 asyncio.Future[
None],
1098 list[asyncio.Future[
None]],
1105 timeout: float |
None,
1107 list[asyncio.Future[
None]],
1108 asyncio.TimerHandle |
None,
1109 asyncio.Future[
None] |
None,
1111 """Return a list of futures to wait for.
1113 The list will contain the stop future.
1115 If timeout is set, a timeout future and handle will be created
1116 and will be added to the list of futures.
1118 timeout_handle: asyncio.TimerHandle |
None =
None
1119 timeout_future: asyncio.Future[
None] |
None =
None
1120 futures: list[asyncio.Future[
None]] = [self.
_stop_stop]
1122 timeout_future = self.
_hass_hass.loop.create_future()
1123 timeout_handle = self.
_hass_hass.loop.call_later(
1124 timeout, _set_result_unless_done, timeout_future
1126 futures.append(timeout_future)
1127 return futures, timeout_handle, timeout_future
1130 """Wait for a trigger event."""
1133 self.
_step_log_step_log(
"wait for trigger", timeout)
1137 "remaining": timeout,
1151 done = self.
_hass_hass.loop.create_future()
1152 futures.append(done)
1154 async
def async_done(
1155 variables: dict[str, Any], context: Context |
None =
None
1158 self.
_variables_variables[
"wait"][
"completed"] =
True
1159 self.
_variables_variables[
"wait"][
"trigger"] = variables[
"trigger"]
1162 def log_cb(level: int, msg: str, **kwargs: Any) ->
None:
1163 self.
_log_log(msg, level=level, **kwargs)
1167 self._action[CONF_WAIT_FOR_TRIGGER],
1172 variables=variables,
1174 if not remove_triggers:
1178 futures, timeout_handle, timeout_future, remove_triggers
1182 """Handle timeout."""
1183 self.
_variables_variables[
"wait"][
"remaining"] = 0.0
1184 if not self._action.
get(CONF_CONTINUE_ON_TIMEOUT,
True):
1185 self.
_log_log(_TIMEOUT_MSG)
1187 raise _AbortScript
from TimeoutError()
1191 futures: list[asyncio.Future[
None]],
1192 timeout_handle: asyncio.TimerHandle |
None,
1193 timeout_future: asyncio.Future[
None] |
None,
1194 unsub: Callable[[],
None],
1197 await asyncio.wait(futures, return_when=asyncio.FIRST_COMPLETED)
1198 if timeout_future
and timeout_future.done():
1201 if timeout_future
and not timeout_future.done()
and timeout_handle:
1202 timeout_handle.cancel()
1207 """Set a variable value."""
1208 self.
_step_log_step_log(
"setting variables")
1209 self.
_variables_variables = self._action[CONF_VARIABLES].async_render(
1214 """Set conversation response."""
1215 self.
_step_log_step_log(
"setting conversation response")
1221 variables=self.
_variables_variables, parse_result=
False
1226 """Stop script execution."""
1227 stop = self._action[CONF_STOP]
1228 error = self._action.
get(CONF_ERROR,
False)
1231 self.
_log_log(
"Error script sequence: %s", stop)
1234 self.
_log_log(
"Stop script sequence: %s", stop)
1235 if CONF_RESPONSE_VARIABLE
in self._action:
1237 response = self.
_variables_variables[self._action[CONF_RESPONSE_VARIABLE]]
1238 except KeyError
as ex:
1240 f
"Response variable '{self._action[CONF_RESPONSE_VARIABLE]}' "
1247 @async_trace_path("sequence")
1249 """Run a sequence."""
1250 sequence = await self.
_script_script._async_get_sequence_script(self.
_step_step)
1253 @async_trace_path("parallel")
1255 """Run a sequence in parallel."""
1256 scripts = await self.
_script_script._async_get_parallel_scripts(self.
_step_step)
1258 async
def async_run_with_trace(idx: int, script: Script) ->
None:
1259 """Run a script with a trace path."""
1260 trace_path_stack_cv.set(copy(trace_path_stack_cv.get()))
1264 results = await asyncio.gather(
1265 *(async_run_with_trace(idx, script)
for idx, script
in enumerate(scripts)),
1266 return_exceptions=
True,
1268 for result
in results:
1269 if isinstance(result, Exception):
1273 """Execute a script."""
1274 result = await self._async_run_long_action(
1275 self.
_hass_hass.async_create_task_internal(
1279 if result
and result.conversation_response
is not UNDEFINED:
1284 """Manage queued Script sequence run."""
1286 lock_acquired =
False
1293 async
with async_interrupt.interrupt(self.
_stop_stop, ScriptStoppedError,
None):
1294 await self.
_script_script._queue_lck.acquire()
1295 except ScriptStoppedError
as ex:
1298 raise asyncio.CancelledError
from ex
1306 self.
_script_script._queue_lck.release()
1313 """Stop running Script objects started after shutdown."""
1315 hass, _SHUTDOWN_MAX_WAIT, partial(_async_stop_scripts_after_shutdown, hass)
1320 hass: HomeAssistant, point_in_time: datetime
1322 """Stop running Script objects started after shutdown."""
1323 hass.data[DATA_NEW_SCRIPT_RUNS_NOT_ALLOWED] =
None
1325 script
for script
in hass.data[DATA_SCRIPTS]
if script[
"instance"].is_running
1328 names =
", ".join([script[
"instance"].name
for script
in running_scripts])
1329 _LOGGER.warning(
"Stopping scripts running too long after shutdown: %s", names)
1330 await asyncio.gather(
1332 create_eager_task(script[
"instance"].
async_stop(update_state=
False))
1333 for script
in running_scripts
1339 """Stop running Script objects started before shutdown."""
1344 for script
in hass.data[DATA_SCRIPTS]
1345 if script[
"instance"].is_running
and script[
"started_before_shutdown"]
1348 names =
", ".join([script[
"instance"].name
for script
in running_scripts])
1349 _LOGGER.debug(
"Stopping scripts running at shutdown: %s", names)
1350 await asyncio.gather(
1352 create_eager_task(script[
"instance"].
async_stop())
1353 for script
in running_scripts
1358 type _VarsType = dict[str, Any] | Mapping[str, Any] | MappingProxyType[str, Any]
1362 """Extract referenced IDs."""
1364 if not isinstance(data, dict):
1367 item_ids = data.get(key)
1372 if isinstance(item_ids, str):
1375 for item_id
in item_ids:
1380 choices: list[tuple[list[ConditionCheckerType], Script]]
1381 default: Script |
None
1385 if_conditions: list[ConditionCheckerType]
1387 if_else: Script |
None
1392 """Container with the result of a script run."""
1394 conversation_response: str |
None | UndefinedType
1395 service_response: ServiceResponse
1396 variables: dict[str, Any]
1400 """Representation of a script."""
1404 hass: HomeAssistant,
1405 sequence: Sequence[dict[str, Any]],
1410 change_listener: Callable[[], Any] |
None =
None,
1411 copy_variables: bool =
False,
1412 log_exceptions: bool =
True,
1413 logger: logging.Logger |
None =
None,
1414 max_exceeded: str = DEFAULT_MAX_EXCEEDED,
1415 max_runs: int = DEFAULT_MAX,
1416 running_description: str |
None =
None,
1417 script_mode: str = DEFAULT_SCRIPT_MODE,
1418 top_level: bool =
True,
1419 variables: ScriptVariables |
None =
None,
1421 """Initialize the script."""
1422 if not (all_scripts := hass.data.get(DATA_SCRIPTS)):
1423 all_scripts = hass.data[DATA_SCRIPTS] = []
1424 hass.bus.async_listen_once(
1425 EVENT_HOMEASSISTANT_STOP, partial(_async_stop_scripts_at_shutdown, hass)
1430 {
"instance": self,
"started_before_shutdown":
not hass.is_stopping}
1432 if DATA_SCRIPT_BREAKPOINTS
not in hass.data:
1433 hass.data[DATA_SCRIPT_BREAKPOINTS] = {}
1443 None if change_listener
is None else HassJob(change_listener)
1453 self._runs: list[_ScriptRun] = []
1456 if script_mode == SCRIPT_MODE_QUEUED:
1458 self._config_cache: dict[frozenset[tuple[str, str]], ConditionCheckerType] = {}
1459 self._repeat_script: dict[int, Script] = {}
1460 self._choose_data: dict[int, _ChooseData] = {}
1461 self._if_data: dict[int, _IfData] = {}
1462 self._parallel_scripts: dict[int, list[Script]] = {}
1463 self._sequence_scripts: dict[int, Script] = {}
1470 """Return the change_listener."""
1473 @change_listener.setter
1475 """Update the change_listener."""
1487 self.
_logger_logger = logging.getLogger(f
"{__name__}.{slugify(self.name)}")
1490 """Update logger."""
1492 for script
in self._repeat_script.values():
1493 script.update_logger(self.
_logger_logger)
1494 for parallel_scripts
in self._parallel_scripts.values():
1495 for parallel_script
in parallel_scripts:
1496 parallel_script.update_logger(self.
_logger_logger)
1497 for choose_data
in self._choose_data.values():
1498 for _, script
in choose_data[
"choices"]:
1499 script.update_logger(self.
_logger_logger)
1500 if choose_data[
"default"]
is not None:
1502 for if_data
in self._if_data.values():
1504 if if_data[
"if_else"]
is not None:
1513 if sub_script.is_running:
1519 """Return true if script is on."""
1520 return len(self._runs) > 0
1524 """Return the number of current runs."""
1525 return len(self._runs)
1529 """Return true if the current mode support max."""
1530 return self.
script_modescript_mode
in (SCRIPT_MODE_PARALLEL, SCRIPT_MODE_QUEUED)
1534 """Return a set of referenced labels."""
1535 referenced_labels: set[str] = set()
1536 Script._find_referenced_target(ATTR_LABEL_ID, referenced_labels, self.
sequencesequence)
1537 return referenced_labels
1541 """Return a set of referenced fooors."""
1542 referenced_floors: set[str] = set()
1543 Script._find_referenced_target(ATTR_FLOOR_ID, referenced_floors, self.
sequencesequence)
1544 return referenced_floors
1548 """Return a set of referenced areas."""
1549 referenced_areas: set[str] = set()
1550 Script._find_referenced_target(ATTR_AREA_ID, referenced_areas, self.
sequencesequence)
1551 return referenced_areas
1555 target: Literal[
"area_id",
"floor_id",
"label_id"],
1556 referenced: set[str],
1557 sequence: Sequence[dict[str, Any]],
1559 """Find referenced target in a sequence."""
1560 for step
in sequence:
1561 action = cv.determine_script_action(step)
1563 if action == cv.SCRIPT_ACTION_CALL_SERVICE:
1565 step.get(CONF_TARGET),
1566 step.get(CONF_SERVICE_DATA),
1567 step.get(CONF_SERVICE_DATA_TEMPLATE),
1571 elif action == cv.SCRIPT_ACTION_CHOOSE:
1572 for choice
in step[CONF_CHOOSE]:
1573 Script._find_referenced_target(
1574 target, referenced, choice[CONF_SEQUENCE]
1576 if CONF_DEFAULT
in step:
1577 Script._find_referenced_target(
1578 target, referenced, step[CONF_DEFAULT]
1581 elif action == cv.SCRIPT_ACTION_IF:
1582 Script._find_referenced_target(target, referenced, step[CONF_THEN])
1583 if CONF_ELSE
in step:
1584 Script._find_referenced_target(target, referenced, step[CONF_ELSE])
1586 elif action == cv.SCRIPT_ACTION_PARALLEL:
1587 for script
in step[CONF_PARALLEL]:
1588 Script._find_referenced_target(
1589 target, referenced, script[CONF_SEQUENCE]
1594 """Return a set of referenced devices."""
1595 referenced_devices: set[str] = set()
1596 Script._find_referenced_devices(referenced_devices, self.
sequencesequence)
1597 return referenced_devices
1601 referenced: set[str], sequence: Sequence[dict[str, Any]]
1603 for step
in sequence:
1604 action = cv.determine_script_action(step)
1606 if action == cv.SCRIPT_ACTION_CALL_SERVICE:
1608 step.get(CONF_TARGET),
1609 step.get(CONF_SERVICE_DATA),
1610 step.get(CONF_SERVICE_DATA_TEMPLATE),
1614 elif action == cv.SCRIPT_ACTION_CHECK_CONDITION:
1615 referenced |= condition.async_extract_devices(step)
1617 elif action == cv.SCRIPT_ACTION_DEVICE_AUTOMATION:
1618 referenced.add(step[CONF_DEVICE_ID])
1620 elif action == cv.SCRIPT_ACTION_CHOOSE:
1621 for choice
in step[CONF_CHOOSE]:
1622 for cond
in choice[CONF_CONDITIONS]:
1623 referenced |= condition.async_extract_devices(cond)
1624 Script._find_referenced_devices(referenced, choice[CONF_SEQUENCE])
1625 if CONF_DEFAULT
in step:
1626 Script._find_referenced_devices(referenced, step[CONF_DEFAULT])
1628 elif action == cv.SCRIPT_ACTION_IF:
1629 for cond
in step[CONF_IF]:
1630 referenced |= condition.async_extract_devices(cond)
1631 Script._find_referenced_devices(referenced, step[CONF_THEN])
1632 if CONF_ELSE
in step:
1633 Script._find_referenced_devices(referenced, step[CONF_ELSE])
1635 elif action == cv.SCRIPT_ACTION_PARALLEL:
1636 for script
in step[CONF_PARALLEL]:
1637 Script._find_referenced_devices(referenced, script[CONF_SEQUENCE])
1641 """Return a set of referenced entities."""
1642 referenced_entities: set[str] = set()
1643 Script._find_referenced_entities(referenced_entities, self.
sequencesequence)
1644 return referenced_entities
1648 referenced: set[str], sequence: Sequence[dict[str, Any]]
1650 for step
in sequence:
1651 action = cv.determine_script_action(step)
1653 if action == cv.SCRIPT_ACTION_CALL_SERVICE:
1656 step.get(CONF_TARGET),
1657 step.get(CONF_SERVICE_DATA),
1658 step.get(CONF_SERVICE_DATA_TEMPLATE),
1662 elif action == cv.SCRIPT_ACTION_CHECK_CONDITION:
1663 referenced |= condition.async_extract_entities(step)
1665 elif action == cv.SCRIPT_ACTION_ACTIVATE_SCENE:
1666 referenced.add(step[CONF_SCENE])
1668 elif action == cv.SCRIPT_ACTION_CHOOSE:
1669 for choice
in step[CONF_CHOOSE]:
1670 for cond
in choice[CONF_CONDITIONS]:
1671 referenced |= condition.async_extract_entities(cond)
1672 Script._find_referenced_entities(referenced, choice[CONF_SEQUENCE])
1673 if CONF_DEFAULT
in step:
1674 Script._find_referenced_entities(referenced, step[CONF_DEFAULT])
1676 elif action == cv.SCRIPT_ACTION_IF:
1677 for cond
in step[CONF_IF]:
1678 referenced |= condition.async_extract_entities(cond)
1679 Script._find_referenced_entities(referenced, step[CONF_THEN])
1680 if CONF_ELSE
in step:
1681 Script._find_referenced_entities(referenced, step[CONF_ELSE])
1683 elif action == cv.SCRIPT_ACTION_PARALLEL:
1684 for script
in step[CONF_PARALLEL]:
1685 Script._find_referenced_entities(referenced, script[CONF_SEQUENCE])
1688 self, variables: _VarsType |
None =
None, context: Context |
None =
None
1691 asyncio.run_coroutine_threadsafe(
1692 self.
async_runasync_run(variables, context), self.
_hass_hass.loop
1697 run_variables: _VarsType |
None =
None,
1698 context: Context |
None =
None,
1699 started_action: Callable[..., Any] |
None =
None,
1700 ) -> ScriptRunResult |
None:
1704 "Running script requires passing in a context", level=logging.WARNING
1709 if DATA_NEW_SCRIPT_RUNS_NOT_ALLOWED
in self.
_hass_hass.data:
1710 self.
_log_log(
"Home Assistant is shutting down, starting script blocked")
1715 if self.
script_modescript_mode == SCRIPT_MODE_SINGLE:
1717 self.
_log_log(
"Already running", level=LOGSEVERITY[self.
_max_exceeded_max_exceeded])
1723 "Maximum number of runs exceeded",
1735 variables = self.
variablesvariables.async_render(
1740 self.
_log_log(
"Error rendering variables: %s", err, level=logging.ERROR)
1743 variables =
dict(run_variables)
1747 variables[
"context"] = context
1750 variables = cast(dict[str, Any], copy(run_variables))
1753 variables = cast(dict[str, Any], run_variables)
1757 script_stack = script_stack_cv.get()
1759 self.
script_modescript_mode
in (SCRIPT_MODE_RESTART, SCRIPT_MODE_QUEUED)
1760 and script_stack
is not None
1761 and self.
unique_idunique_id
in script_stack
1765 f
"- {name_id.partition('-')[0]}" for name_id
in script_stack
1768 "Disallowed recursion detected, "
1769 f
"{script_stack[-1].partition('-')[0]} tried to start "
1770 f
"{self.domain}.{self.name} which is already running "
1771 "in the current execution path; "
1772 "Traceback (most recent call last):\n"
1773 f
"{"\n
".join(formatted_stack)}",
1774 level=logging.WARNING,
1778 if self.
script_modescript_mode != SCRIPT_MODE_QUEUED:
1781 cls = _QueuedScriptRun
1783 has_existing_runs = bool(self._runs)
1784 self._runs.append(run)
1785 if self.
script_modescript_mode == SCRIPT_MODE_RESTART
and has_existing_runs:
1792 self.
_log_log(
"Restarting")
1793 await self.
async_stopasync_stop(update_state=
False, spare=run)
1801 return await asyncio.shield(create_eager_task(run.async_run()))
1802 except asyncio.CancelledError:
1803 await run.async_stop()
1808 self, aws: list[asyncio.Task[
None]], update_state: bool
1810 await asyncio.wait(aws)
1815 self, update_state: bool =
True, spare: _ScriptRun |
None =
None
1817 """Stop running script."""
1822 create_eager_task(run.async_stop())
for run
in self._runs
if run != spare
1826 await asyncio.shield(create_eager_task(self.
_async_stop_async_stop(aws, update_state)))
1829 config_cache_key = frozenset((k,
str(v))
for k, v
in config.items())
1830 if not (cond := self._config_cache.
get(config_cache_key)):
1831 cond = await condition.async_from_config(self.
_hass_hass, config)
1832 self._config_cache[config_cache_key] = cond
1836 action = self.
sequencesequence[step]
1837 step_name = action.get(CONF_ALIAS, f
"Repeat at step {step+1}")
1840 action[CONF_REPEAT][CONF_SEQUENCE],
1841 f
"{self.name}: {step_name}",
1844 script_mode=SCRIPT_MODE_PARALLEL,
1853 if not (sub_script := self._repeat_script.
get(step)):
1855 self._repeat_script[step] = sub_script
1859 action = self.
sequencesequence[step]
1860 step_name = action.get(CONF_ALIAS, f
"Choose at step {step+1}")
1862 for idx, choice
in enumerate(action[CONF_CHOOSE], start=1):
1865 for config
in choice.get(CONF_CONDITIONS, [])
1867 choice_name = choice.get(CONF_ALIAS, f
"choice {idx}")
1870 choice[CONF_SEQUENCE],
1871 f
"{self.name}: {step_name}: {choice_name}",
1874 script_mode=SCRIPT_MODE_PARALLEL,
1879 sub_script.change_listener = partial(
1882 choices.append((conditions, sub_script))
1884 default_script: Script |
None
1885 if CONF_DEFAULT
in action:
1888 action[CONF_DEFAULT],
1889 f
"{self.name}: {step_name}: default",
1892 script_mode=SCRIPT_MODE_PARALLEL,
1897 default_script.change_listener = partial(
1901 default_script =
None
1903 return {
"choices": choices,
"default": default_script}
1906 if not (choose_data := self._choose_data.
get(step)):
1908 self._choose_data[step] = choose_data
1912 """Prepare data for an if statement."""
1913 action = self.
sequencesequence[step]
1914 step_name = action.get(CONF_ALIAS, f
"If at step {step+1}")
1923 f
"{self.name}: {step_name}",
1926 script_mode=SCRIPT_MODE_PARALLEL,
1933 if CONF_ELSE
in action:
1937 f
"{self.name}: {step_name}",
1940 script_mode=SCRIPT_MODE_PARALLEL,
1945 else_script.change_listener = partial(
1952 if_conditions=conditions,
1953 if_then=then_script,
1954 if_else=else_script,
1958 if not (if_data := self._if_data.
get(step)):
1960 self._if_data[step] = if_data
1964 action = self.
sequencesequence[step]
1965 step_name = action.get(CONF_ALIAS, f
"Parallel action at step {step+1}")
1966 parallel_scripts: list[Script] = []
1967 for idx, parallel_script
in enumerate(action[CONF_PARALLEL], start=1):
1968 parallel_name = parallel_script.get(CONF_ALIAS, f
"parallel {idx}")
1969 parallel_script =
Script(
1971 parallel_script[CONF_SEQUENCE],
1972 f
"{self.name}: {step_name}: {parallel_name}",
1975 script_mode=SCRIPT_MODE_PARALLEL,
1979 copy_variables=
True,
1981 parallel_script.change_listener = partial(
1984 parallel_scripts.append(parallel_script)
1986 return parallel_scripts
1989 if not (parallel_scripts := self._parallel_scripts.
get(step)):
1991 self._parallel_scripts[step] = parallel_scripts
1992 return parallel_scripts
1995 """Prepare a sequence script."""
1996 action = self.
sequencesequence[step]
1997 step_name = action.get(CONF_ALIAS, f
"Sequence action at step {step+1}")
1999 sequence_script =
Script(
2001 action[CONF_SEQUENCE],
2002 f
"{self.name}: {step_name}",
2005 script_mode=SCRIPT_MODE_PARALLEL,
2010 sequence_script.change_listener = partial(
2014 return sequence_script
2017 """Get a (cached) sequence script."""
2018 if not (sequence_script := self._sequence_scripts.
get(step)):
2020 self._sequence_scripts[step] = sequence_script
2021 return sequence_script
2024 self, msg: str, *args: Any, level: int = logging.INFO, **kwargs: Any
2027 args = (self.
namename, *args)
2029 if level == _LOG_EXCEPTION:
2030 self.
_logger_logger.exception(msg, *args, **kwargs)
2032 self.
_logger_logger.log(level, msg, *args, **kwargs)
2037 hass: HomeAssistant, key: str, run_id: str |
None, node: str
2039 """Clear a breakpoint."""
2040 run_id = run_id
or RUN_ID_ANY
2041 breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS]
2042 if key
not in breakpoints
or run_id
not in breakpoints[key]:
2044 breakpoints[key][run_id].discard(node)
2049 """Clear all breakpoints."""
2050 hass.data[DATA_SCRIPT_BREAKPOINTS] = {}
2055 hass: HomeAssistant, key: str, run_id: str |
None, node: str
2057 """Set a breakpoint."""
2058 run_id = run_id
or RUN_ID_ANY
2059 breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS]
2060 if key
not in breakpoints:
2061 breakpoints[key] = {}
2062 if run_id
not in breakpoints[key]:
2063 breakpoints[key][run_id] = set()
2064 breakpoints[key][run_id].
add(node)
2069 """List breakpoints."""
2070 breakpoints = hass.data[DATA_SCRIPT_BREAKPOINTS]
2073 {
"key": key,
"run_id": run_id,
"node": node}
2074 for key
in breakpoints
2075 for run_id
in breakpoints[key]
2076 for node
in breakpoints[key][run_id]
2082 """Continue execution of a halted script."""
2086 signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id)
2087 async_dispatcher_send_internal(hass, signal,
"continue")
2091 def debug_step(hass: HomeAssistant, key: str, run_id: str) ->
None:
2092 """Single step a halted script."""
2096 signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id)
2097 async_dispatcher_send_internal(hass, signal,
"continue")
2101 def debug_stop(hass: HomeAssistant, key: str, run_id: str) ->
None:
2102 """Stop execution of a running or halted script."""
2103 signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id)
2104 async_dispatcher_send_internal(hass, signal,
"stop")
set[str] referenced_labels(self)
None _chain_change_listener(self, Script sub_script)
Callable[..., Any]|None change_listener(self)
set[str] referenced_entities(self)
set[str] referenced_floors(self)
list[Script] _async_prep_parallel_scripts(self, int step)
None update_logger(self, logging.Logger|None logger=None)
list[Script] _async_get_parallel_scripts(self, int step)
None _find_referenced_entities(set[str] referenced, Sequence[dict[str, Any]] sequence)
None __init__(self, HomeAssistant hass, Sequence[dict[str, Any]] sequence, str name, str domain, *Callable[[], Any]|None change_listener=None, bool copy_variables=False, bool log_exceptions=True, logging.Logger|None logger=None, str max_exceeded=DEFAULT_MAX_EXCEEDED, int max_runs=DEFAULT_MAX, str|None running_description=None, str script_mode=DEFAULT_SCRIPT_MODE, bool top_level=True, ScriptVariables|None variables=None)
_ChooseData _async_get_choose_data(self, int step)
_IfData _async_prep_if_data(self, int step)
None _set_logger(self, logging.Logger|None logger=None)
Script _get_repeat_script(self, int step)
None async_stop(self, bool update_state=True, _ScriptRun|None spare=None)
set[str] referenced_devices(self)
None _log(self, str msg, *Any args, int level=logging.INFO, **Any kwargs)
Script _async_get_sequence_script(self, int step)
None _async_stop(self, list[asyncio.Task[None]] aws, bool update_state)
set[str] referenced_areas(self)
Script _async_prep_sequence_script(self, int step)
None run(self, _VarsType|None variables=None, Context|None context=None)
ConditionCheckerType _async_get_condition(self, ConfigType config)
ScriptRunResult|None async_run(self, _VarsType|None run_variables=None, Context|None context=None, Callable[..., Any]|None started_action=None)
_IfData _async_get_if_data(self, int step)
_ChooseData _async_prep_choose_data(self, int step)
None _find_referenced_target(Literal["area_id", "floor_id", "label_id"] target, set[str] referenced, Sequence[dict[str, Any]] sequence)
Script _prep_repeat_script(self, int step)
None _find_referenced_devices(set[str] referenced, Sequence[dict[str, Any]] sequence)
None _async_device_step(self)
tuple[ list[asyncio.Future[None]], asyncio.TimerHandle, asyncio.Future[None],] _async_futures_with_timeout(self, float timeout)
None _async_step(self, bool log_exceptions)
None _step_log(self, str default_message, float|None timeout=None)
None _handle_exception(self, Exception exception, bool continue_on_error, bool log_exceptions)
ConditionCheckerType _async_get_condition(self, ConfigType config)
None _async_variables_step(self)
None _async_wait_for_trigger_step(self)
None _async_handle_timeout(self)
None _log(self, str msg, *Any args, int level=logging.INFO, **Any kwargs)
tuple[ list[asyncio.Future[None]], asyncio.TimerHandle|None, asyncio.Future[None]|None,] _async_futures_with_timeout(self, float|None timeout)
None _async_call_service_step(self)
None _async_wait_with_optional_timeout(self, list[asyncio.Future[None]] futures, asyncio.TimerHandle|None timeout_handle, asyncio.Future[None]|None timeout_future, Callable[[], None] unsub)
None _async_run_script(self, Script script)
None _async_set_conversation_response_step(self)
ScriptRunResult|None async_run(self)
timedelta _get_pos_time_period_template(self, str key)
None _async_set_remaining_time_var(self, asyncio.TimerHandle|None timeout_handle)
None _async_delay_step(self)
None _async_if_step(self)
None __init__(self, HomeAssistant hass, Script script, dict[str, Any] variables, Context|None context, bool log_exceptions)
None _async_scene_step(self)
bool|None _test_conditions(self, list[ConditionCheckerType] conditions, str name, str|None condition_path=None)
tuple[ list[asyncio.Future[None]], None, None,] _async_futures_with_timeout(self, None timeout)
None _async_sequence_step(self)
None _log_exception(self, Exception exception)
None _async_stop_step(self)
float|None _get_timeout_seconds_from_action(self)
None _async_condition_step(self)
None _async_wait_template_step(self)
None _async_event_step(self)
None _async_parallel_step(self)
None _async_repeat_step(self)
None _async_choose_step(self)
None __init__(self, str message, Any response)
bool add(self, _T matcher)
ConfigType async_validate_trigger_config(HomeAssistant hass, ConfigType config)
web.Response get(self, web.Request request, str config_key)
None async_stop(HomeAssistant hass)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
CALLBACK_TYPE async_track_template(HomeAssistant hass, Template template, Callable[[str, State|None, State|None], Coroutine[Any, Any, None]|None] action, TemplateVarsType|None variables=None)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
ConfigType async_validate_action_config(HomeAssistant hass, ConfigType config)
None _referenced_extract_ids(Any data, str key, set[str] found)
list[ConfigType] async_validate_actions_config(HomeAssistant hass, list[ConfigType] actions)
vol.Schema make_script_schema(Mapping[Any, Any] schema, str default_script_mode, int extra=vol.PREVENT_EXTRA)
AsyncGenerator[TraceElement] trace_action(HomeAssistant hass, _ScriptRun script_run, asyncio.Future[None] stop, dict[str, Any] variables)
None debug_continue(HomeAssistant hass, str key, str run_id)
TraceElement action_trace_append(dict[str, Any] variables, str path)
None _set_result_unless_done(asyncio.Future[None] future)
None _async_stop_scripts_at_shutdown(HomeAssistant hass, Event event)
None breakpoint_set(HomeAssistant hass, str key, str|None run_id, str node)
None _schedule_stop_scripts_after_shutdown(HomeAssistant hass)
list[dict[str, Any]] breakpoint_list(HomeAssistant hass)
None breakpoint_clear_all(HomeAssistant hass)
None breakpoint_clear(HomeAssistant hass, str key, str|None run_id, str node)
None debug_step(HomeAssistant hass, str key, str run_id)
None _async_stop_scripts_after_shutdown(HomeAssistant hass, datetime point_in_time)
None debug_stop(HomeAssistant hass, str key, str run_id)
None script_execution_set(str reason, ServiceResponse response=None)
None trace_stack_pop(ContextVar[list[Any]|None] trace_stack_var)
Generator[None] trace_path(str|list[str] suffix)
None trace_update_result(**Any kwargs)
tuple[str, str]|None trace_id_get()
None trace_set_result(**Any kwargs)
None trace_append_element(TraceElement trace_element, int|None maxlen=None)
CALLBACK_TYPE|None async_initialize_triggers(HomeAssistant hass, list[ConfigType] trigger_config, Callable action, str domain, str name, Callable log_cb, bool home_assistant_start=False, TemplateVarsType variables=None)