From a633baf481091d7522b1769b26bcd6c81c6b8fca Mon Sep 17 00:00:00 2001 From: Petro31 <35082313+Petro31@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:14:17 -0400 Subject: [PATCH] Block Capability and State attributes for template sensor attributes but allow `options` (#180396) --- homeassistant/components/template/sensor.py | 8 ++- .../components/template/validators.py | 4 ++ tests/components/template/test_sensor.py | 59 ++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 68564d972958..6cce22e5d372 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -16,6 +16,8 @@ from homeassistant.components.sensor import ( STATE_CLASSES_SCHEMA, RestoreSensor, SensorDeviceClass, + SensorEntityCapabilityAttribute, + SensorEntityStateAttribute, SensorExtraStoredData, SensorStateClass, ) @@ -70,7 +72,11 @@ SENSOR_COMMON_SCHEMA = vol.Schema( } ) -_BLOCKED_ATTRIBUTES = tcv.BlockedTemplateAttributes(device_class=True) +_BLOCKED_ATTRIBUTES = tcv.BlockedTemplateAttributes( + attributes=(SensorEntityCapabilityAttribute, SensorEntityStateAttribute), + device_class=True, + allowed_attributes=(SensorEntityCapabilityAttribute.OPTIONS,), +) SENSOR_YAML_SCHEMA = vol.All( vol.Schema( diff --git a/homeassistant/components/template/validators.py b/homeassistant/components/template/validators.py index b9520bb59894..d4cf2baf9ef2 100644 --- a/homeassistant/components/template/validators.py +++ b/homeassistant/components/template/validators.py @@ -32,6 +32,7 @@ class BlockedTemplateAttributes: *, attributes: tuple[type[StrEnum], ...] | type[StrEnum] | None = None, device_class: bool = False, + allowed_attributes: tuple[StrEnum | str, ...] | None = None, ) -> None: """Initialize.""" blocked_attributes: set[str] @@ -42,6 +43,9 @@ class BlockedTemplateAttributes: else: blocked_attributes = set(attributes) + if allowed_attributes: + blocked_attributes -= set(allowed_attributes) + if device_class: blocked_attributes.add("device_class") diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 4cba1e0a9c0a..42e13f893bf2 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -2,6 +2,8 @@ from asyncio import Event from datetime import datetime +from itertools import chain +from typing import Any from unittest.mock import ANY, patch import pytest @@ -9,6 +11,10 @@ from syrupy.assertion import SnapshotAssertion from homeassistant.bootstrap import async_from_config_dict from homeassistant.components import sensor, template +from homeassistant.components.sensor import ( + SensorEntityCapabilityAttribute, + SensorEntityStateAttribute, +) from homeassistant.components.template import DOMAIN from homeassistant.const import ( ATTR_ENTITY_PICTURE, @@ -1953,7 +1959,19 @@ async def test_attributes_template( ) -@pytest.mark.parametrize("attribute", ["device_class"]) +@pytest.mark.parametrize( + "attribute", + [ + *list( + chain( + set(SensorEntityCapabilityAttribute) + - {SensorEntityCapabilityAttribute.OPTIONS}, + SensorEntityStateAttribute, + ) + ), + "device_class", + ], +) @pytest.mark.parametrize( "style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER] ) @@ -1979,3 +1997,42 @@ async def test_attributes_template_with_blocked_attributes( error = f"Unsupported attribute(s) found for {TEST_SENSOR.entity_id}: {attribute}" assert error in caplog.text + + +@pytest.mark.parametrize( + ("attribute", "set_state", "expected"), + [(SensorEntityCapabilityAttribute.OPTIONS, ["a"], ["a"])], +) +@pytest.mark.parametrize( + "style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER] +) +async def test_attributes_template_with_allowed_attributes( + hass: HomeAssistant, + style: ConfigurationStyle, + attribute: str, + set_state: Any, + expected: Any, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test blocked attributes for a single attributes template.""" + await setup_entity( + hass, + TEST_SENSOR, + style, + 1, + { + "state": "{{ 'x' }}", + "attributes": f"{{{{ dict({attribute}=state_attr('{TEST_ATTRIBUTE_ENTITY_ID}', '{attribute}')) }}}}", + }, + ) + + await async_trigger( + hass, TEST_ATTRIBUTE_ENTITY_ID, "anything", {attribute: set_state} + ) + + state = hass.states.get(TEST_SENSOR.entity_id) + assert state.state == "x" + assert state.attributes[attribute] == expected + + error = f"Unsupported attribute(s) found for {TEST_SENSOR.entity_id}: {attribute}" + assert error not in caplog.text