From ba48da76787c083af0d8fe4b21d283d21de758d2 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Tue, 14 May 2024 14:44:21 +0200 Subject: [PATCH] Allow templates for enabling automation triggers (#114458) * Allow templates for enabling automation triggers * Test exception for non-limited template * Use `cv.template` instead of `cv.template_complex` * skip trigger with invalid enable template instead of returning and thus not evaluating other triggers --- homeassistant/helpers/config_validation.py | 2 +- homeassistant/helpers/trigger.py | 15 +++- tests/helpers/test_trigger.py | 84 ++++++++++++++++++++++ 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index bf20a2d7f5f..697810e21aa 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1648,7 +1648,7 @@ TRIGGER_BASE_SCHEMA = vol.Schema( vol.Required(CONF_PLATFORM): str, vol.Optional(CONF_ID): str, vol.Optional(CONF_VARIABLES): SCRIPT_VARIABLES_SCHEMA, - vol.Optional(CONF_ENABLED): boolean, + vol.Optional(CONF_ENABLED): vol.Any(boolean, template), } ) diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index 5c2b372bb7d..a0abbaa390c 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -27,11 +27,12 @@ from homeassistant.core import ( callback, is_callback, ) -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, TemplateError from homeassistant.loader import IntegrationNotFound, async_get_integration from homeassistant.util.async_ import create_eager_task from homeassistant.util.hass_dict import HassKey +from .template import Template from .typing import ConfigType, TemplateVarsType _PLATFORM_ALIASES = { @@ -312,8 +313,16 @@ async def async_initialize_triggers( triggers: list[asyncio.Task[CALLBACK_TYPE]] = [] for idx, conf in enumerate(trigger_config): # Skip triggers that are not enabled - if not conf.get(CONF_ENABLED, True): - continue + if CONF_ENABLED in conf: + enabled = conf[CONF_ENABLED] + if isinstance(enabled, Template): + try: + enabled = enabled.async_render(variables, limited=True) + except TemplateError as err: + log_cb(logging.ERROR, f"Error rendering enabled template: {err}") + continue + if not enabled: + continue platform = await _async_get_trigger_platform(hass, conf) trigger_id = conf.get(CONF_ID, f"{idx}") diff --git a/tests/helpers/test_trigger.py b/tests/helpers/test_trigger.py index 0a15cf9a330..0ab02b8c4dc 100644 --- a/tests/helpers/test_trigger.py +++ b/tests/helpers/test_trigger.py @@ -110,6 +110,90 @@ async def test_if_disabled_trigger_not_firing( assert len(calls) == 1 +async def test_trigger_enabled_templates( + hass: HomeAssistant, calls: list[ServiceCall] +) -> None: + """Test triggers enabled by template.""" + assert await async_setup_component( + hass, + "automation", + { + "automation": { + "trigger": [ + { + "enabled": "{{ 'some text' }}", + "platform": "event", + "event_type": "truthy_template_trigger_event", + }, + { + "enabled": "{{ 3 == 4 }}", + "platform": "event", + "event_type": "falsy_template_trigger_event", + }, + { + "enabled": False, # eg. from a blueprints input defaulting to `false` + "platform": "event", + "event_type": "falsy_trigger_event", + }, + { + "enabled": "some text", # eg. from a blueprints input value + "platform": "event", + "event_type": "truthy_trigger_event", + }, + ], + "action": { + "service": "test.automation", + }, + } + }, + ) + + hass.bus.async_fire("falsy_template_trigger_event") + await hass.async_block_till_done() + assert not calls + + hass.bus.async_fire("falsy_trigger_event") + await hass.async_block_till_done() + assert not calls + + hass.bus.async_fire("truthy_template_trigger_event") + await hass.async_block_till_done() + assert len(calls) == 1 + + hass.bus.async_fire("truthy_trigger_event") + await hass.async_block_till_done() + assert len(calls) == 2 + + +async def test_trigger_enabled_template_limited( + hass: HomeAssistant, calls: list[ServiceCall], caplog: pytest.LogCaptureFixture +) -> None: + """Test triggers enabled invalid template.""" + assert await async_setup_component( + hass, + "automation", + { + "automation": { + "trigger": [ + { + "enabled": "{{ states('sensor.limited') }}", # only limited template supported + "platform": "event", + "event_type": "test_event", + }, + ], + "action": { + "service": "test.automation", + }, + } + }, + ) + + hass.bus.async_fire("test_event") + await hass.async_block_till_done() + assert not calls + assert "Error rendering enabled template" in caplog.text + + async def test_trigger_alias( hass: HomeAssistant, calls: list[ServiceCall], caplog: pytest.LogCaptureFixture ) -> None: