3 from __future__
import annotations
6 from collections
import defaultdict
7 from collections.abc
import Callable, Coroutine
8 from dataclasses
import dataclass, field
11 from typing
import Any, Protocol, TypedDict, cast
13 import voluptuous
as vol
35 from .template
import Template
36 from .typing
import ConfigType, TemplateVarsType
39 "device":
"device_automation",
40 "event":
"homeassistant",
41 "numeric_state":
"homeassistant",
42 "state":
"homeassistant",
43 "time_pattern":
"homeassistant",
44 "time":
"homeassistant",
47 DATA_PLUGGABLE_ACTIONS: HassKey[defaultdict[tuple, PluggableActionsEntry]] =
HassKey(
53 """Define the format of trigger modules.
55 Each module must define either TRIGGER_SCHEMA or async_validate_trigger_config.
58 TRIGGER_SCHEMA: vol.Schema
61 self, hass: HomeAssistant, config: ConfigType
63 """Validate config."""
69 action: TriggerActionType,
70 trigger_info: TriggerInfo,
72 """Attach a trigger."""
76 """Protocol type for trigger action callback."""
80 run_variables: dict[str, Any],
81 context: Context |
None =
None,
83 """Define action callback type."""
95 """Information about trigger."""
99 home_assistant_start: bool
100 variables: TemplateVarsType
101 trigger_data: TriggerData
104 @dataclass(slots=True)
106 """Holder to keep track of all plugs and actions for a given trigger."""
108 plugs: set[PluggableAction] = field(default_factory=set)
112 HassJob[[dict[str, Any], Context |
None], Coroutine[Any, Any,
None]],
115 ] = field(default_factory=dict)
119 """A pluggable action handler."""
121 _entry: PluggableActionsEntry |
None =
None
123 def __init__(self, update: CALLBACK_TYPE |
None =
None) ->
None:
124 """Initialize a pluggable action.
126 :param update: callback triggered whenever triggers are attached or removed.
131 """Return if we have something attached."""
132 return bool(self.
_entry_entry
and self.
_entry_entry.actions)
136 """Run update function if one exists."""
143 """Return the pluggable actions registry."""
144 if data := hass.data.get(DATA_PLUGGABLE_ACTIONS):
146 data = hass.data[DATA_PLUGGABLE_ACTIONS] = defaultdict(PluggableActionsEntry)
153 trigger: dict[str, str],
154 action: TriggerActionType,
155 variables: dict[str, Any],
157 """Attach an action to a trigger entry.
159 Existing or future plugs registered will be attached.
161 reg = PluggableAction.async_get_registry(hass)
162 key =
tuple(sorted(trigger.items()))
166 for plug
in entry.plugs:
167 plug.async_run_update()
170 def _remove() -> None:
171 """Remove this action attachment, and disconnect all plugs."""
172 del entry.actions[_remove]
174 if not entry.actions
and not entry.plugs:
177 job =
HassJob(action, f
"trigger {trigger} {variables}")
178 entry.actions[_remove] = (job, variables)
185 self, hass: HomeAssistant, trigger: dict[str, str]
187 """Register plug in the global plugs dictionary."""
189 reg = PluggableAction.async_get_registry(hass)
190 key =
tuple(sorted(trigger.items()))
192 self.
_entry_entry.plugs.add(self)
195 def _remove() -> None:
196 """Remove plug from registration.
198 Clean up entry if there are no actions or plugs registered.
201 self.
_entry_entry.plugs.remove(self)
202 if not self.
_entry_entry.actions
and not self.
_entry_entry.plugs:
209 self, hass: HomeAssistant, context: Context |
None =
None
211 """Run all actions."""
213 for job, variables
in self.
_entry_entry.actions.values():
214 task = hass.async_run_hass_job(job, variables, context)
220 hass: HomeAssistant, config: ConfigType
221 ) -> TriggerProtocol:
222 platform_and_sub_type = config[CONF_PLATFORM].split(
".")
223 platform = platform_and_sub_type[0]
224 platform = _PLATFORM_ALIASES.get(platform, platform)
227 except IntegrationNotFound:
228 raise vol.Invalid(f
"Invalid trigger '{platform}' specified")
from None
230 return await integration.async_get_platform(
"trigger")
233 f
"Integration '{platform}' does not provide trigger support"
238 hass: HomeAssistant, trigger_config: list[ConfigType]
239 ) -> list[ConfigType]:
240 """Validate triggers."""
242 for conf
in trigger_config:
244 if hasattr(platform,
"async_validate_trigger_config"):
245 conf = await platform.async_validate_trigger_config(hass, conf)
247 conf = platform.TRIGGER_SCHEMA(conf)
253 hass: HomeAssistant, action: Callable, conf: ConfigType
255 """Wrap trigger action with extra vars if configured.
257 If action is a coroutine function, a coroutine function will be returned.
258 If action is a callback, a callback will be returned.
260 if CONF_VARIABLES
not in conf:
265 while isinstance(check_func, functools.partial):
266 check_func = check_func.func
268 wrapper_func: Callable[...,
None] | Callable[..., Coroutine[Any, Any,
None]]
269 if asyncio.iscoroutinefunction(check_func):
270 async_action = cast(Callable[..., Coroutine[Any, Any,
None]], action)
272 @functools.wraps(async_action)
273 async
def async_with_vars(
274 run_variables: dict[str, Any], context: Context |
None =
None
276 """Wrap action with extra vars."""
277 trigger_variables = conf[CONF_VARIABLES]
278 run_variables.update(trigger_variables.async_render(hass, run_variables))
279 await action(run_variables, context)
281 wrapper_func = async_with_vars
285 @functools.wraps(action)
287 run_variables: dict[str, Any], context: Context |
None =
None
289 """Wrap action with extra vars."""
290 trigger_variables = conf[CONF_VARIABLES]
291 run_variables.update(trigger_variables.async_render(hass, run_variables))
292 action(run_variables, context)
295 with_vars = callback(with_vars)
297 wrapper_func = with_vars
304 trigger_config: list[ConfigType],
309 home_assistant_start: bool =
False,
310 variables: TemplateVarsType =
None,
311 ) -> CALLBACK_TYPE |
None:
312 """Initialize triggers."""
313 triggers: list[asyncio.Task[CALLBACK_TYPE]] = []
314 for idx, conf
in enumerate(trigger_config):
316 if CONF_ENABLED
in conf:
317 enabled = conf[CONF_ENABLED]
318 if isinstance(enabled, Template):
320 enabled = enabled.async_render(variables, limited=
True)
321 except TemplateError
as err:
322 log_cb(logging.ERROR, f
"Error rendering enabled template: {err}")
328 trigger_id = conf.get(CONF_ID, f
"{idx}")
329 trigger_idx = f
"{idx}"
330 trigger_alias = conf.get(CONF_ALIAS)
331 trigger_data =
TriggerData(id=trigger_id, idx=trigger_idx, alias=trigger_alias)
335 home_assistant_start=home_assistant_start,
337 trigger_data=trigger_data,
342 platform.async_attach_trigger(
348 attach_results = await asyncio.gather(*triggers, return_exceptions=
True)
349 removes: list[Callable[[],
None]] = []
351 for result
in attach_results:
352 if isinstance(result, HomeAssistantError):
353 log_cb(logging.ERROR, f
"Got error '{result}' when setting up triggers for")
354 elif isinstance(result, Exception):
355 log_cb(logging.ERROR,
"Error setting up trigger", exc_info=result)
356 elif isinstance(result, BaseException):
357 raise result
from None
360 logging.ERROR,
"Unknown error while setting up trigger (empty result)"
363 removes.append(result)
368 log_cb(logging.INFO,
"Initialized trigger")
371 def remove_triggers() -> None:
372 """Remove triggers."""
373 for remove
in removes:
376 return remove_triggers
CALLBACK_TYPE async_register(self, HomeAssistant hass, dict[str, str] trigger)
dict[tuple, PluggableActionsEntry] async_get_registry(HomeAssistant hass)
None async_run_update(self)
None async_run(self, HomeAssistant hass, Context|None context=None)
None __init__(self, CALLBACK_TYPE|None update=None)
CALLBACK_TYPE async_attach_trigger(HomeAssistant hass, dict[str, str] trigger, TriggerActionType action, dict[str, Any] variables)
Any __call__(self, dict[str, Any] run_variables, Context|None context=None)
ConfigType async_validate_trigger_config(self, HomeAssistant hass, ConfigType config)
bool remove(self, _T matcher)
CALLBACK_TYPE async_attach_trigger(HomeAssistant hass, ConfigType config, TriggerActionType action, TriggerInfo trigger_info)
bool is_callback(Callable[..., Any] func)
TriggerProtocol _async_get_trigger_platform(HomeAssistant hass, ConfigType config)
list[ConfigType] async_validate_trigger_config(HomeAssistant hass, list[ConfigType] trigger_config)
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)
Callable _trigger_action_wrapper(HomeAssistant hass, Callable action, ConfigType conf)
Integration async_get_integration(HomeAssistant hass, str domain)