From 3dc7b75e4b7c5b740ea3257b170255c8bd47447b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Beye?= Date: Mon, 26 May 2025 17:34:13 +0200 Subject: [PATCH] Allow nested schema validation in event automation trigger (#126771) * Allow nested schema validation in event automation trigger * Fix rfxtrx device trigger * Don't create nested voluptuous schemas * Fix editing mistake * Fix format --------- Co-authored-by: Erik Montnemery Co-authored-by: Martin Hjelmare --- .../homeassistant/triggers/event.py | 14 +++--- .../components/rfxtrx/device_trigger.py | 2 +- .../homeassistant/triggers/test_event.py | 45 +++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index 985e4819b24..8065c23c5c1 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -75,14 +75,18 @@ async def async_attach_trigger( event_data.update( template.render_complex(config[CONF_EVENT_DATA], variables, limited=True) ) - # Build the schema or a an items view if the schema is simple - # and does not contain sub-dicts. We explicitly do not check for - # list like the context data below since lists are a special case - # only for context data. (see test test_event_data_with_list) + + # For performance reasons, we want to avoid using a voluptuous schema here + # unless required. Thus, if possible, we try to use a simple items comparison + # For that, we explicitly do not check for list like the context data below + # since lists are a special case only used for context data, see test + # test_event_data_with_list. Otherwise, we build a volutupus schema, see test + # test_event_data_with_list_nested if any(isinstance(value, dict) for value in event_data.values()): event_data_schema = vol.Schema( - {vol.Required(key): value for key, value in event_data.items()}, + event_data, extra=vol.ALLOW_EXTRA, + required=True, ) else: # Use a simple items comparison if possible diff --git a/homeassistant/components/rfxtrx/device_trigger.py b/homeassistant/components/rfxtrx/device_trigger.py index 35c1944948b..fe9e0da0d52 100644 --- a/homeassistant/components/rfxtrx/device_trigger.py +++ b/homeassistant/components/rfxtrx/device_trigger.py @@ -97,7 +97,7 @@ async def async_attach_trigger( if config[CONF_TYPE] == CONF_TYPE_COMMAND: event_data["values"] = {"Command": config[CONF_SUBTYPE]} elif config[CONF_TYPE] == CONF_TYPE_STATUS: - event_data["values"] = {"Status": config[CONF_SUBTYPE]} + event_data["values"] = {"Sensor Status": config[CONF_SUBTYPE]} event_config = event_trigger.TRIGGER_SCHEMA( { diff --git a/tests/components/homeassistant/triggers/test_event.py b/tests/components/homeassistant/triggers/test_event.py index 293a9007175..5536db1eb5e 100644 --- a/tests/components/homeassistant/triggers/test_event.py +++ b/tests/components/homeassistant/triggers/test_event.py @@ -517,6 +517,51 @@ async def test_event_data_with_list( await hass.async_block_till_done() assert len(service_calls) == 1 + # don't match if property doesn't exist at all + hass.bus.async_fire("test_event", {"other_attr": [1, 2]}) + await hass.async_block_till_done() + assert len(service_calls) == 1 + + +async def test_event_data_with_list_nested( + hass: HomeAssistant, service_calls: list[ServiceCall] +) -> None: + """Test the (non)firing of event when the data schema has nested lists.""" + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": { + "platform": "event", + "event_type": "test_event", + "event_data": {"service_data": {"some_attr": [1, 2]}}, + "context": {}, + }, + "action": {"service": "test.automation"}, + } + }, + ) + + hass.bus.async_fire("test_event", {"service_data": {"some_attr": [1, 2]}}) + await hass.async_block_till_done() + assert len(service_calls) == 1 + + # don't match a single value + hass.bus.async_fire("test_event", {"service_data": {"some_attr": 1}}) + await hass.async_block_till_done() + assert len(service_calls) == 1 + + # don't match a containing list + hass.bus.async_fire("test_event", {"service_data": {"some_attr": [1, 2, 3]}}) + await hass.async_block_till_done() + assert len(service_calls) == 1 + + # don't match if property doesn't exist at all + hass.bus.async_fire("test_event", {"service_data": {"other_attr": [1, 2]}}) + await hass.async_block_till_done() + assert len(service_calls) == 1 + @pytest.mark.parametrize( "event_type", ["state_reported", ["test_event", "state_reported"]]