mirror of
https://github.com/home-assistant/core.git
synced 2026-09-17 14:08:42 +01:00
183 lines
5.6 KiB
Python
183 lines
5.6 KiB
Python
"""Common trigger classes and constants."""
|
|
|
|
import abc
|
|
import asyncio
|
|
from collections.abc import Mapping
|
|
from dataclasses import dataclass
|
|
from typing import Any, Protocol
|
|
|
|
import probatio
|
|
|
|
from homeassistant.const import CONF_OPTIONS, CONF_TARGET
|
|
from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant, callback
|
|
from homeassistant.helpers import config_validation as cv
|
|
from homeassistant.helpers.typing import ConfigType
|
|
from homeassistant.util.hass_dict import HassKey
|
|
|
|
TRIGGERS: HassKey[dict[str, str]] = HassKey("triggers")
|
|
|
|
_TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
|
|
{
|
|
probatio.Optional(CONF_OPTIONS): object,
|
|
probatio.Optional(CONF_TARGET): cv.TARGET_FIELDS,
|
|
}
|
|
)
|
|
|
|
|
|
@dataclass(slots=True, frozen=True)
|
|
class TriggerConfig:
|
|
"""Trigger config."""
|
|
|
|
key: str # The key used to identify the trigger, e.g. "zwave.event"
|
|
target: dict[str, Any] | None = None
|
|
options: dict[str, Any] | None = None
|
|
|
|
|
|
class TriggerActionRunner(Protocol):
|
|
"""Protocol type for the trigger action runner helper callback."""
|
|
|
|
@callback
|
|
def __call__(
|
|
self,
|
|
extra_trigger_payload: dict[str, Any],
|
|
description: str,
|
|
context: Context | None = None,
|
|
) -> asyncio.Task[Any]:
|
|
"""Define trigger action runner type.
|
|
|
|
Returns:
|
|
A Task that allows awaiting for the action to finish.
|
|
"""
|
|
|
|
|
|
@dataclass(slots=True, frozen=True)
|
|
class NotTriggeredInfo:
|
|
"""Diagnostics describing why a trigger evaluated a change but did not fire.
|
|
|
|
Passed by a trigger to its ``did_not_trigger`` reporter, the sibling of the
|
|
action runner that is called - in certain interesting cases - when the
|
|
trigger does not fire. ``reason`` is a stable, machine-readable code; the
|
|
optional ``data`` carries the evaluated context for the trace.
|
|
"""
|
|
|
|
reason: str
|
|
data: Mapping[str, Any] | None = None
|
|
|
|
def as_dict(self) -> dict[str, Any]:
|
|
"""Return a JSON-serializable dict for storing in a trace."""
|
|
result: dict[str, Any] = {"reason": self.reason}
|
|
if self.data is not None:
|
|
result["data"] = dict(self.data)
|
|
return result
|
|
|
|
|
|
class TriggerNotTriggeredReporter(Protocol):
|
|
"""Protocol type for the did_not_trigger reporter passed to a trigger runner.
|
|
|
|
A trigger calls this to report that it evaluated a relevant change but
|
|
decided not to fire, supplying diagnostics for tracing.
|
|
"""
|
|
|
|
@callback
|
|
def __call__(
|
|
self,
|
|
info: NotTriggeredInfo,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
"""Report that the trigger did not fire."""
|
|
|
|
|
|
class TriggerActionPayloadBuilder(Protocol):
|
|
"""Protocol type for the trigger action payload builder."""
|
|
|
|
def __call__(
|
|
self, extra_trigger_payload: dict[str, Any], description: str
|
|
) -> dict[str, Any]:
|
|
"""Define trigger action payload builder type."""
|
|
|
|
|
|
class TriggerAction(Protocol):
|
|
"""Protocol type for trigger action callback."""
|
|
|
|
async def __call__(
|
|
self, run_variables: dict[str, Any], context: Context | None = None
|
|
) -> Any:
|
|
"""Define action callback type."""
|
|
|
|
|
|
class Trigger(abc.ABC):
|
|
"""Trigger class."""
|
|
|
|
_hass: HomeAssistant
|
|
|
|
@classmethod
|
|
async def async_validate_complete_config(
|
|
cls, hass: HomeAssistant, complete_config: ConfigType
|
|
) -> ConfigType:
|
|
"""Validate complete config.
|
|
|
|
The complete config includes fields that are generic to all triggers,
|
|
such as the alias or the ID.
|
|
This method should be overridden by triggers that need to migrate
|
|
from the old-style config.
|
|
"""
|
|
complete_config = _TRIGGER_SCHEMA(complete_config)
|
|
|
|
specific_config: ConfigType = {}
|
|
for key in (CONF_OPTIONS, CONF_TARGET):
|
|
if key in complete_config:
|
|
specific_config[key] = complete_config.pop(key)
|
|
specific_config = await cls.async_validate_config(hass, specific_config)
|
|
|
|
for key in (CONF_OPTIONS, CONF_TARGET):
|
|
if key in specific_config:
|
|
complete_config[key] = specific_config[key]
|
|
|
|
return complete_config
|
|
|
|
@classmethod
|
|
@abc.abstractmethod
|
|
async def async_validate_config(
|
|
cls, hass: HomeAssistant, config: ConfigType
|
|
) -> ConfigType:
|
|
"""Validate config."""
|
|
|
|
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
|
|
"""Initialize trigger."""
|
|
self._hass = hass
|
|
|
|
async def async_attach_action(
|
|
self,
|
|
action: TriggerAction,
|
|
action_payload_builder: TriggerActionPayloadBuilder,
|
|
*,
|
|
did_not_trigger: TriggerNotTriggeredReporter | None = None,
|
|
) -> CALLBACK_TYPE:
|
|
"""Attach the trigger to an action.
|
|
|
|
The optional ``did_not_trigger`` reporter is the sibling of the action
|
|
runner: triggers may call it - in certain interesting cases - when they
|
|
evaluate a relevant change but decide not to fire.
|
|
"""
|
|
|
|
@callback
|
|
def run_action(
|
|
extra_trigger_payload: dict[str, Any],
|
|
description: str,
|
|
context: Context | None = None,
|
|
) -> asyncio.Task[Any]:
|
|
"""Run action with trigger variables."""
|
|
|
|
payload = action_payload_builder(extra_trigger_payload, description)
|
|
return self._hass.async_create_task(action(payload, context))
|
|
|
|
return await self.async_attach_runner(run_action, did_not_trigger)
|
|
|
|
@abc.abstractmethod
|
|
async def async_attach_runner(
|
|
self,
|
|
run_action: TriggerActionRunner,
|
|
did_not_trigger: TriggerNotTriggeredReporter | None = None,
|
|
) -> CALLBACK_TYPE:
|
|
"""Attach the trigger to an action runner."""
|