Add attributes to all template platforms (#178762)

This commit is contained in:
Petro31
2026-08-12 14:58:41 +02:00
committed by GitHub
parent 0a4c342404
commit 1a9e5beab3
32 changed files with 832 additions and 101 deletions
@@ -11,6 +11,7 @@ from homeassistant.components.alarm_control_panel import (
ENTITY_ID_FORMAT,
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
AlarmControlPanelEntityStateAttribute,
AlarmControlPanelState,
CodeFormat,
)
@@ -38,7 +39,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -96,8 +97,8 @@ ALARM_CONTROL_PANEL_COMMON_SCHEMA = vol.Schema(
ALARM_CONTROL_PANEL_YAML_SCHEMA = ALARM_CONTROL_PANEL_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA
).extend(
make_template_entity_common_modern_schema(
ALARM_CONTROL_PANEL_DOMAIN, DEFAULT_NAME
make_template_entity_common_schema(
ALARM_CONTROL_PANEL_DOMAIN, DEFAULT_NAME, AlarmControlPanelEntityStateAttribute
).schema
)
@@ -43,7 +43,7 @@ from .helpers import (
)
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
make_template_entity_common_modern_attributes_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -67,9 +67,7 @@ BINARY_SENSOR_COMMON_SCHEMA = vol.Schema(
)
BINARY_SENSOR_YAML_SCHEMA = BINARY_SENSOR_COMMON_SCHEMA.extend(
make_template_entity_common_modern_attributes_schema(
BINARY_SENSOR_DOMAIN, DEFAULT_NAME
).schema
make_template_entity_common_schema(BINARY_SENSOR_DOMAIN, DEFAULT_NAME).schema
)
BINARY_SENSOR_CONFIG_ENTRY_SCHEMA = BINARY_SENSOR_COMMON_SCHEMA.extend(
+6 -2
View File
@@ -25,7 +25,7 @@ from .const import CONF_PRESS, DOMAIN
from .helpers import async_setup_template_entry, async_setup_template_platform
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
@@ -41,7 +41,11 @@ BUTTON_YAML_SCHEMA = vol.Schema(
vol.Required(CONF_PRESS): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
}
).extend(make_template_entity_common_modern_schema(BUTTON_DOMAIN, DEFAULT_NAME).schema)
).extend(
make_template_entity_common_schema(
BUTTON_DOMAIN, DEFAULT_NAME, block_device_class=True
).schema
)
BUTTON_CONFIG_ENTRY_SCHEMA = vol.Schema(
{
+8 -2
View File
@@ -13,6 +13,7 @@ from homeassistant.components.cover import (
ENTITY_ID_FORMAT,
CoverEntity,
CoverEntityFeature,
CoverEntityStateAttribute,
CoverState,
)
from homeassistant.config_entries import ConfigEntry
@@ -37,7 +38,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -98,7 +99,12 @@ COVER_YAML_SCHEMA = vol.All(
.extend(COVER_COMMON_SCHEMA.schema)
.extend(TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA)
.extend(
make_template_entity_common_modern_schema(COVER_DOMAIN, DEFAULT_NAME).schema
make_template_entity_common_schema(
COVER_DOMAIN,
DEFAULT_NAME,
CoverEntityStateAttribute,
block_device_class=True,
).schema
),
cv.has_at_least_one_key(OPEN_ACTION, POSITION_ACTION),
)
@@ -10,7 +10,10 @@ from homeassistant.components import zone
from homeassistant.components.device_tracker import (
DOMAIN as DEVICE_TRACKER_DOMAIN,
ENTITY_ID_FORMAT,
DeviceTrackerEntityCapabilityAttribute,
DeviceTrackerEntityStateAttribute,
TrackerEntity,
TrackerEntityStateAttribute,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
@@ -32,7 +35,7 @@ from .helpers import (
)
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -117,8 +120,14 @@ TRACKER_COMMON_SCHEMA = vol.Schema(
TRACKER_YAML_SCHEMA = vol.All(
_validate_in_zones_or_lat_and_lon,
TRACKER_COMMON_SCHEMA.extend(
make_template_entity_common_modern_schema(
DEVICE_TRACKER_DOMAIN, DEFAULT_NAME
make_template_entity_common_schema(
DEVICE_TRACKER_DOMAIN,
DEFAULT_NAME,
(
DeviceTrackerEntityCapabilityAttribute,
DeviceTrackerEntityStateAttribute,
TrackerEntityStateAttribute,
),
).schema
),
)
+2 -4
View File
@@ -31,7 +31,7 @@ from .helpers import (
)
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
make_template_entity_common_modern_attributes_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -54,9 +54,7 @@ EVENT_COMMON_SCHEMA = vol.Schema(
)
EVENT_YAML_SCHEMA = EVENT_COMMON_SCHEMA.extend(
make_template_entity_common_modern_attributes_schema(
EVENT_DOMAIN, DEFAULT_NAME
).schema
make_template_entity_common_schema(EVENT_DOMAIN, DEFAULT_NAME).schema
)
+8 -2
View File
@@ -13,7 +13,9 @@ from homeassistant.components.fan import (
DOMAIN as FAN_DOMAIN,
ENTITY_ID_FORMAT,
FanEntity,
FanEntityCapabilityAttribute,
FanEntityFeature,
FanEntityStateAttribute,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_STATE
@@ -38,7 +40,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -91,7 +93,11 @@ FAN_COMMON_SCHEMA = vol.Schema(
)
FAN_YAML_SCHEMA = FAN_COMMON_SCHEMA.extend(TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA).extend(
make_template_entity_common_modern_schema(FAN_DOMAIN, DEFAULT_NAME).schema
make_template_entity_common_schema(
FAN_DOMAIN,
DEFAULT_NAME,
(FanEntityStateAttribute, FanEntityCapabilityAttribute),
).schema
)
FAN_CONFIG_ENTRY_SCHEMA = FAN_COMMON_SCHEMA.extend(
+4 -3
View File
@@ -9,6 +9,7 @@ from homeassistant.components.image import (
DOMAIN as IMAGE_DOMAIN,
ENTITY_ID_FORMAT,
ImageEntity,
ImageEntityStateAttribute,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_URL, CONF_VERIFY_SSL
@@ -27,7 +28,7 @@ from .entity import AbstractTemplateEntity
from .helpers import async_setup_template_entry, async_setup_template_platform
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
make_template_entity_common_modern_attributes_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -44,8 +45,8 @@ IMAGE_YAML_SCHEMA = vol.Schema(
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
}
).extend(
make_template_entity_common_modern_attributes_schema(
IMAGE_DOMAIN, DEFAULT_NAME
make_template_entity_common_schema(
IMAGE_DOMAIN, DEFAULT_NAME, ImageEntityStateAttribute
).schema
)
+10 -2
View File
@@ -24,7 +24,9 @@ from homeassistant.components.light import (
ENTITY_ID_FORMAT,
ColorMode,
LightEntity,
LightEntityCapabilityAttribute,
LightEntityFeature,
LightEntityStateAttribute,
filter_supported_color_modes,
)
from homeassistant.config_entries import ConfigEntry
@@ -57,7 +59,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -134,7 +136,13 @@ LIGHT_COMMON_SCHEMA = vol.Schema(
LIGHT_YAML_SCHEMA = LIGHT_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA
).extend(make_template_entity_common_modern_schema(LIGHT_DOMAIN, DEFAULT_NAME).schema)
).extend(
make_template_entity_common_schema(
LIGHT_DOMAIN,
DEFAULT_NAME,
(LightEntityCapabilityAttribute, LightEntityStateAttribute),
).schema
)
LIGHT_CONFIG_ENTRY_SCHEMA = LIGHT_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
+5 -2
View File
@@ -10,6 +10,7 @@ from homeassistant.components.lock import (
ENTITY_ID_FORMAT,
LockEntity,
LockEntityFeature,
LockEntityStateAttribute,
LockState,
)
from homeassistant.config_entries import ConfigEntry
@@ -36,7 +37,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -67,7 +68,9 @@ LOCK_COMMON_SCHEMA = vol.Schema(
)
LOCK_YAML_SCHEMA = LOCK_COMMON_SCHEMA.extend(TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA).extend(
make_template_entity_common_modern_schema(LOCK_DOMAIN, DEFAULT_NAME).schema
make_template_entity_common_schema(
LOCK_DOMAIN, DEFAULT_NAME, LockEntityStateAttribute
).schema
)
LOCK_CONFIG_ENTRY_SCHEMA = LOCK_COMMON_SCHEMA.extend(
+10 -2
View File
@@ -11,6 +11,7 @@ from homeassistant.components.number import (
DEVICE_CLASSES_SCHEMA,
DOMAIN as NUMBER_DOMAIN,
ENTITY_ID_FORMAT,
NumberEntityCapabilityAttribute,
NumberExtraStoredData,
RestoreNumber,
)
@@ -40,7 +41,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -66,7 +67,14 @@ NUMBER_COMMON_SCHEMA = vol.Schema(
NUMBER_YAML_SCHEMA = NUMBER_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA
).extend(make_template_entity_common_modern_schema(NUMBER_DOMAIN, DEFAULT_NAME).schema)
).extend(
make_template_entity_common_schema(
NUMBER_DOMAIN,
DEFAULT_NAME,
NumberEntityCapabilityAttribute,
block_device_class=True,
).schema
)
NUMBER_CONFIG_ENTRY_SCHEMA = NUMBER_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
+50 -51
View File
@@ -1,12 +1,15 @@
"""Shared schemas for config entry and YAML config items."""
from collections.abc import Callable
from enum import StrEnum
from itertools import chain
from typing import TypeVar
import voluptuous as vol
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_ENTITY_PICTURE_TEMPLATE,
CONF_ICON,
CONF_ICON_TEMPLATE,
CONF_NAME,
CONF_OPTIMISTIC,
CONF_UNIQUE_ID,
@@ -15,26 +18,20 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv, selector
from .const import (
CONF_ATTRIBUTE_TEMPLATES,
CONF_ATTRIBUTES,
CONF_AVAILABILITY,
CONF_AVAILABILITY_TEMPLATE,
CONF_DEFAULT_ENTITY_ID,
CONF_PICTURE,
)
_AttributeEnum = TypeVar("_AttributeEnum", bound=type[StrEnum])
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA = vol.Schema(
{
vol.Optional(CONF_AVAILABILITY): cv.template,
}
)
TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA = vol.Schema(
{
vol.Optional(CONF_ATTRIBUTES): vol.Schema({cv.string: cv.template}),
}
)
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): cv.template,
@@ -47,32 +44,48 @@ TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA = {
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
}
TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA_LEGACY = vol.Schema(
{
vol.Optional(CONF_ATTRIBUTE_TEMPLATES, default={}): vol.Schema(
{cv.string: cv.template}
),
}
)
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY = vol.Schema(
{
vol.Optional(CONF_AVAILABILITY_TEMPLATE): cv.template,
}
)
def _blocked_attributes(
default_name: str,
blocked_attributes: tuple[_AttributeEnum, ...] | _AttributeEnum | None,
block_device_class: bool,
) -> Callable[[dict], dict]:
TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY = vol.Schema(
{
vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
}
).extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY.schema)
def validate(obj: dict):
if blocked_attributes is None and not block_device_class:
return obj
_blocked_attributes: set[str]
if blocked_attributes is None:
_blocked_attributes = set()
elif isinstance(blocked_attributes, tuple):
_blocked_attributes = set(chain(*blocked_attributes))
else:
_blocked_attributes = set(blocked_attributes)
if block_device_class:
_blocked_attributes.add("device_class")
if blocked := (_blocked_attributes.intersection(set(obj.keys()))):
raise vol.Invalid(
f"Unsupported attribute(s) found for {default_name}: {', '.join(blocked)}"
)
return obj
return validate
def make_template_entity_base_schema(domain: str, default_name: str) -> vol.Schema:
def make_template_entity_common_schema(
domain: str,
default_name: str,
blocked_attributes: tuple[_AttributeEnum, ...] | _AttributeEnum | None = None,
block_device_class: bool = False,
) -> vol.Schema:
"""Return a schema with default name."""
return vol.Schema(
{
vol.Optional(CONF_AVAILABILITY): cv.template,
vol.Optional(CONF_DEFAULT_ENTITY_ID): vol.All(
cv.entity_id, cv.entity_domain(domain)
),
@@ -80,28 +93,14 @@ def make_template_entity_base_schema(domain: str, default_name: str) -> vol.Sche
vol.Optional(CONF_NAME, default=default_name): cv.template,
vol.Optional(CONF_PICTURE): cv.template,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
def make_template_entity_common_modern_schema(
domain: str,
default_name: str,
) -> vol.Schema:
"""Return a schema with default name."""
return vol.Schema(
{
vol.Optional(CONF_AVAILABILITY): cv.template,
vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
vol.Optional(CONF_ATTRIBUTES): vol.Schema(
vol.All(
{cv.string: cv.template},
_blocked_attributes(
default_name, blocked_attributes, block_device_class
),
)
),
}
).extend(make_template_entity_base_schema(domain, default_name).schema)
def make_template_entity_common_modern_attributes_schema(
domain: str,
default_name: str,
) -> vol.Schema:
"""Return a schema with default name."""
return make_template_entity_common_modern_schema(domain, default_name).extend(
TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA.schema
)
+7 -2
View File
@@ -10,6 +10,7 @@ from homeassistant.components.select import (
DOMAIN as SELECT_DOMAIN,
ENTITY_ID_FORMAT,
SelectEntity,
SelectEntityCapabilityAttribute,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_OPTIONS, CONF_STATE
@@ -33,7 +34,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -56,7 +57,11 @@ SELECT_COMMON_SCHEMA = vol.Schema(
SELECT_YAML_SCHEMA = SELECT_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA
).extend(make_template_entity_common_modern_schema(SELECT_DOMAIN, DEFAULT_NAME).schema)
).extend(
make_template_entity_common_schema(
SELECT_DOMAIN, DEFAULT_NAME, SelectEntityCapabilityAttribute
).schema
)
SELECT_CONFIG_ENTRY_SCHEMA = SELECT_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
+2 -6
View File
@@ -39,7 +39,7 @@ from .helpers import (
)
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
make_template_entity_common_modern_attributes_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -77,11 +77,7 @@ SENSOR_YAML_SCHEMA = vol.All(
}
)
.extend(SENSOR_COMMON_SCHEMA.schema)
.extend(
make_template_entity_common_modern_attributes_schema(
SENSOR_DOMAIN, DEFAULT_NAME
).schema
),
.extend(make_template_entity_common_schema(SENSOR_DOMAIN, DEFAULT_NAME).schema),
validate_last_reset,
)
+6 -2
View File
@@ -31,7 +31,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -53,7 +53,11 @@ SWITCH_COMMON_SCHEMA = vol.Schema(
SWITCH_YAML_SCHEMA = SWITCH_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA
).extend(make_template_entity_common_modern_schema(SWITCH_DOMAIN, DEFAULT_NAME).schema)
).extend(
make_template_entity_common_schema(
SWITCH_DOMAIN, DEFAULT_NAME, block_device_class=True
).schema
)
SWITCH_CONFIG_ENTRY_SCHEMA = SWITCH_COMMON_SCHEMA.extend(
+7 -2
View File
@@ -34,7 +34,7 @@ from .helpers import (
)
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -76,7 +76,12 @@ UPDATE_COMMON_SCHEMA = vol.Schema(
)
UPDATE_YAML_SCHEMA = UPDATE_COMMON_SCHEMA.extend(
make_template_entity_common_modern_schema(UPDATE_DOMAIN, DEFAULT_NAME).schema
make_template_entity_common_schema(
UPDATE_DOMAIN,
DEFAULT_NAME,
UpdateEntityStateAttribute,
block_device_class=True,
).schema
)
UPDATE_CONFIG_ENTRY_SCHEMA = UPDATE_COMMON_SCHEMA.extend(
+2 -4
View File
@@ -43,7 +43,7 @@ from .helpers import (
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_attributes_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -101,9 +101,7 @@ VACUUM_COMMON_SCHEMA = vol.Schema(
VACUUM_YAML_SCHEMA = vol.All(
VACUUM_COMMON_SCHEMA.extend(TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA).extend(
make_template_entity_common_modern_attributes_schema(
VACUUM_DOMAIN, DEFAULT_NAME
).schema
make_template_entity_common_schema(VACUUM_DOMAIN, DEFAULT_NAME).schema
),
cv.key_dependency(CONF_SEGMENTS, CONF_UNIQUE_ID),
cv.key_dependency(CONF_CLEAN_SEGMENTS, CONF_UNIQUE_ID),
+8 -3
View File
@@ -28,6 +28,7 @@ from homeassistant.components.weather import (
Forecast,
WeatherEntity,
WeatherEntityFeature,
WeatherEntityStateAttribute,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@@ -65,7 +66,7 @@ from .helpers import (
)
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
make_template_entity_common_modern_schema,
make_template_entity_common_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
@@ -227,12 +228,16 @@ WEATHER_YAML_SCHEMA = (
)
.extend(WEATHER_COMMON_LEGACY_SCHEMA.schema)
.extend(
make_template_entity_common_modern_schema(WEATHER_DOMAIN, DEFAULT_NAME).schema
make_template_entity_common_schema(
WEATHER_DOMAIN, DEFAULT_NAME, WeatherEntityStateAttribute
).schema
)
)
WEATHER_MODERN_YAML_SCHEMA = WEATHER_COMMON_MODERN_SCHEMA.extend(
make_template_entity_common_modern_schema(WEATHER_DOMAIN, DEFAULT_NAME).schema
make_template_entity_common_schema(
WEATHER_DOMAIN, DEFAULT_NAME, WeatherEntityStateAttribute
).schema
)
WEATHER_CONFIG_ENTRY_SCHEMA = WEATHER_COMMON_MODERN_SCHEMA.extend(
+81 -1
View File
@@ -2,6 +2,7 @@
from dataclasses import dataclass
from enum import Enum, StrEnum
from itertools import chain
import pytest
@@ -21,6 +22,8 @@ from tests.common import (
)
from tests.conftest import WebSocketGenerator
_TEST_EXTRA_ATTRIBUTES_ENTITY_ID = "sensor.test_extra_attributes"
class ConfigurationStyle(Enum):
"""Configuration Styles for template testing."""
@@ -44,7 +47,7 @@ def make_test_trigger(*entities: str) -> dict:
"trigger": [
{
"trigger": "state",
"entity_id": list(entities),
"entity_id": list(chain(entities, (_TEST_EXTRA_ATTRIBUTES_ENTITY_ID,))),
},
{"platform": "event", "event_type": "test_event"},
],
@@ -413,3 +416,80 @@ async def setup_restore_template_entity(
**config,
},
)
async def assert_extra_template_attributes(
hass: HomeAssistant,
platform_setup: TemplatePlatformSetup,
style: ConfigurationStyle,
config: ConfigType,
) -> None:
"""Test extra template attributes and attribute order."""
# Trigger attributes are resolved in order, Modern are not.
setup_attributes = {
ConfigurationStyle.MODERN: {},
ConfigurationStyle.TRIGGER: {
"base": "{{ state_attr('sensor.test_extra_attributes', 'base') or 0 }}",
"plus_one": "{{ base + 1 }}",
},
}
await setup_entity(
hass,
platform_setup,
style,
1,
{
**config,
"attributes": {
"static": "{{ 'static' }}",
"dynamic": "It {{ state_attr('sensor.test_extra_attributes', 'dynamic') }}.",
**setup_attributes[style],
},
},
)
await async_trigger(
hass,
_TEST_EXTRA_ATTRIBUTES_ENTITY_ID,
"anything",
{
"dynamic": "",
"base": 1,
},
)
state = hass.states.get(platform_setup.entity_id)
assert state.attributes["static"] == "static"
assert state.attributes["dynamic"] == "It ."
# Assert attribute order for trigger entities
for attr, value in (
("base", 1),
("plus_one", 2),
):
assert (
attr not in state.attributes and style == ConfigurationStyle.MODERN
) or state.attributes[attr] == value
await async_trigger(
hass,
_TEST_EXTRA_ATTRIBUTES_ENTITY_ID,
"anything",
{
"dynamic": "works",
"base": 2,
},
)
state = hass.states.get(platform_setup.entity_id)
assert state.attributes["static"] == "static"
assert state.attributes["dynamic"] == "It works."
for attr, value in (
("base", 2),
("plus_one", 3),
):
assert (
attr not in state.attributes and style == ConfigurationStyle.MODERN
) or state.attributes[attr] == value
@@ -8,8 +8,10 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components import template
from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_DOMAIN,
AlarmControlPanelEntityStateAttribute,
AlarmControlPanelState,
)
from homeassistant.components.template.alarm_control_panel import DEFAULT_NAME
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -21,6 +23,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -790,3 +793,41 @@ async def test_invalid_availability_template_keeps_component_available(
assert hass.states.get(TEST_PANEL.entity_id).state != STATE_UNAVAILABLE
error = "UndefinedError: 'x' is undefined"
assert error in caplog_setup_text or error in caplog.text
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass, TEST_PANEL, style, {"state": "{{ 'disarmed' }}"}
)
@pytest.mark.parametrize("attribute", list(AlarmControlPanelEntityStateAttribute))
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute: AlarmControlPanelEntityStateAttribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_PANEL,
style,
0,
{
"state": "{{ 'disarmed' }}",
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+30
View File
@@ -8,6 +8,7 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.components.template import DOMAIN
from homeassistant.components.template.button import DEFAULT_NAME
from homeassistant.components.template.const import CONF_PICTURE
from homeassistant.const import (
ATTR_ENTITY_PICTURE,
@@ -29,6 +30,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
async_trigger,
make_test_action,
setup_and_test_nested_unique_id,
@@ -381,3 +383,31 @@ async def test_invalid_availability_template_keeps_component_available(
"""Test that an invalid availability keeps the device available."""
assert hass.states.get(TEST_BUTTON.entity_id).state != STATE_UNAVAILABLE
assert "UndefinedError: 'x' is undefined" in caplog_setup_text
async def test_extra_template_attributes(hass: HomeAssistant) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass, TEST_BUTTON, ConfigurationStyle.MODERN, {"press": []}
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_BUTTON,
ConfigurationStyle.MODERN,
0,
{
"press": [],
"attributes": {"device_class": "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: device_class"
in caplog.text
)
+44
View File
@@ -9,8 +9,10 @@ from homeassistant.components.cover import (
ATTR_TILT_POSITION,
DOMAIN as COVER_DOMAIN,
CoverEntityFeature,
CoverEntityStateAttribute,
CoverState,
)
from homeassistant.components.template.cover import DEFAULT_NAME
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_CLOSE_COVER,
@@ -35,6 +37,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -1286,3 +1289,44 @@ async def test_restore_state(
assert state.state == final_state
assert state.attributes["current_position"] == 75
assert state.attributes["current_tilt_position"] == 75
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass, TEST_COVER, style, {"state": "{{ 'open' }}", **COVER_ACTIONS}
)
@pytest.mark.parametrize(
"attribute", [*list(CoverEntityStateAttribute), "device_class"]
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute: CoverEntityStateAttribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_COVER,
style,
0,
{
"state": "{{ 'open' }}",
**COVER_ACTIONS,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
@@ -1,11 +1,13 @@
"""The tests for the Template device_tracker platform."""
from itertools import chain
from typing import Any
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components import device_tracker, template, zone
from homeassistant.components.template.device_tracker import DEFAULT_NAME
from homeassistant.const import (
ATTR_ENTITY_PICTURE,
ATTR_ICON,
@@ -21,6 +23,7 @@ from homeassistant.helpers.typing import ConfigType
from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -786,3 +789,50 @@ async def test_restore_state(
assert state.attributes["latitude"] == 32.88
assert state.attributes["longitude"] == 117.24
assert state.attributes["gps_accuracy"] == 20.0
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass, TEST_TRACKER, style, TEST_MINIMUM_REQUIREMENTS
)
@pytest.mark.parametrize(
"attribute",
list(
chain(
device_tracker.DeviceTrackerEntityCapabilityAttribute,
device_tracker.DeviceTrackerEntityStateAttribute,
device_tracker.TrackerEntityStateAttribute,
)
),
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_TRACKER,
style,
0,
{
**TEST_MINIMUM_REQUIREMENTS,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+47
View File
@@ -1,5 +1,6 @@
"""The tests for the Template fan platform."""
from itertools import chain
from typing import Any
import pytest
@@ -14,9 +15,12 @@ from homeassistant.components.fan import (
ATTR_PRESET_MODE,
DIRECTION_FORWARD,
DIRECTION_REVERSE,
FanEntityCapabilityAttribute,
FanEntityFeature,
FanEntityStateAttribute,
NotValidPresetModeError,
)
from homeassistant.components.template.fan import DEFAULT_NAME
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import entity_registry as er
@@ -26,6 +30,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -1611,3 +1616,45 @@ async def test_restore_state(
assert state.attributes["preset_mode"] == "low"
assert state.attributes["oscillating"] is True
assert state.attributes["direction"] == DIRECTION_REVERSE
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass, TEST_FAN, style, {"state": "{{ 'on' }}", **OPTIMISTIC_ON_OFF_ACTIONS}
)
@pytest.mark.parametrize(
"attribute",
list(chain(FanEntityCapabilityAttribute, FanEntityStateAttribute)),
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_FAN,
style,
0,
{
"state": "{{ 'on' }}",
**OPTIMISTIC_ON_OFF_ACTIONS,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+61
View File
@@ -11,17 +11,27 @@ import respx
from syrupy.assertion import SnapshotAssertion
from homeassistant import setup
from homeassistant.components import image
from homeassistant.components.input_text import (
ATTR_VALUE as INPUT_TEXT_ATTR_VALUE,
DOMAIN as INPUT_TEXT_DOMAIN,
SERVICE_SET_VALUE as INPUT_TEXT_SERVICE_SET_VALUE,
)
from homeassistant.components.template import DOMAIN
from homeassistant.components.template.image import DEFAULT_NAME
from homeassistant.const import ATTR_ENTITY_PICTURE, CONF_ENTITY_ID, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util import dt as dt_util
from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_extra_template_attributes,
make_test_trigger,
setup_entity,
)
from tests.common import MockConfigEntry, assert_setup_component
from tests.typing import ClientSessionGenerator
@@ -29,6 +39,14 @@ _DEFAULT = object()
_TEST_IMAGE = "image.template_image"
_URL_INPUT_TEXT = "input_text.url"
TEST_STATE_ENTITY_ID = "sensor.test_state"
TEST_IMAGE = TemplatePlatformSetup(
image.DOMAIN,
"test_template_image",
make_test_trigger(TEST_STATE_ENTITY_ID, _TEST_IMAGE, _URL_INPUT_TEXT),
)
@pytest.fixture
def imgbytes_jpg():
@@ -582,3 +600,46 @@ async def test_device_id(
template_entity = entity_registry.async_get("image.my_template")
assert template_entity is not None
assert template_entity.device_id == device_entry.id
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass,
TEST_IMAGE,
style,
{
"url": "{{ 'http://example.com' }}",
},
)
@pytest.mark.parametrize("attribute", list(image.ImageEntityStateAttribute))
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute: image.ImageEntityStateAttribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_IMAGE,
style,
0,
{
"url": "{{ 'http://example.com' }}",
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+47
View File
@@ -1,5 +1,6 @@
"""The tests for the Template light platform."""
from itertools import chain
from typing import Any
import pytest
@@ -18,8 +19,11 @@ from homeassistant.components.light import (
ATTR_TRANSITION,
ATTR_XY_COLOR,
ColorMode,
LightEntityCapabilityAttribute,
LightEntityFeature,
LightEntityStateAttribute,
)
from homeassistant.components.template.light import DEFAULT_NAME
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
@@ -38,6 +42,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -2231,3 +2236,45 @@ async def test_saving_state(
"supported_color_modes": ["brightness"],
"xy_color": None,
}
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass, TEST_LIGHT, style, {"state": "{{ 'on' }}", **ON_OFF_ACTIONS}
)
@pytest.mark.parametrize(
"attribute",
list(chain(LightEntityCapabilityAttribute, LightEntityStateAttribute)),
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_LIGHT,
style,
0,
{
"state": "{{ 'on' }}",
**ON_OFF_ACTIONS,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+53 -1
View File
@@ -7,7 +7,12 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant import setup
from homeassistant.components import lock, template
from homeassistant.components.lock import LockEntityFeature, LockState
from homeassistant.components.lock import (
LockEntityFeature,
LockEntityStateAttribute,
LockState,
)
from homeassistant.components.template.lock import DEFAULT_NAME
from homeassistant.const import (
ATTR_CODE,
ATTR_ENTITY_ID,
@@ -24,6 +29,7 @@ from homeassistant.helpers.typing import ConfigType
from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -1155,3 +1161,49 @@ async def test_restore_state(
assert_state_and_attributes(
hass, TEST_LOCK, LockState.UNLOCKED, {"code_format": "\\\\d+"}
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass,
TEST_LOCK,
style,
{"state": "{{ 'locked' }}", **LOCK_ACTION, **UNLOCK_ACTION},
)
@pytest.mark.parametrize(
"attribute",
list(LockEntityStateAttribute),
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_LOCK,
style,
0,
{
"state": "{{ 'locked' }}",
**LOCK_ACTION,
**UNLOCK_ACTION,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+47
View File
@@ -13,9 +13,11 @@ from homeassistant.components.number import (
ATTR_VALUE as NUMBER_ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE as NUMBER_SERVICE_SET_VALUE,
NumberEntityCapabilityAttribute,
)
from homeassistant.components.template import DOMAIN
from homeassistant.components.template.const import CONF_PICTURE
from homeassistant.components.template.number import DEFAULT_NAME
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_ENTITY_PICTURE,
@@ -34,6 +36,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -747,3 +750,47 @@ async def test_restore_state(
"30.0",
{"step": 3, "min": 3, "max": 60},
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass,
TEST_NUMBER,
style,
TEST_REQUIRED,
)
@pytest.mark.parametrize(
"attribute",
[*list(NumberEntityCapabilityAttribute), "device_class"],
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_NUMBER,
style,
0,
{
**TEST_REQUIRED,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+47
View File
@@ -12,9 +12,11 @@ from homeassistant.components.select import (
ATTR_OPTIONS as SELECT_ATTR_OPTIONS,
DOMAIN as SELECT_DOMAIN,
SERVICE_SELECT_OPTION as SELECT_SERVICE_SELECT_OPTION,
SelectEntityCapabilityAttribute,
)
from homeassistant.components.template import DOMAIN
from homeassistant.components.template.const import CONF_PICTURE
from homeassistant.components.template.select import DEFAULT_NAME
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_ENTITY_PICTURE,
@@ -32,6 +34,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -685,3 +688,47 @@ async def test_restore_state(
"options": ["something", "anything", "something_new"],
},
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass,
TEST_SELECT,
style,
TEST_OPTIONS,
)
@pytest.mark.parametrize(
"attribute",
list(SelectEntityCapabilityAttribute),
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_SELECT,
style,
0,
{
**TEST_OPTIONS,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+38
View File
@@ -7,6 +7,7 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components import switch, template
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.template.switch import DEFAULT_NAME
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
@@ -25,6 +26,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
async_get_flow_preview_state,
async_trigger,
make_test_action,
@@ -826,3 +828,39 @@ async def test_not_optimistic(hass: HomeAssistant, expected: str) -> None:
state = hass.states.get(TEST_SWITCH.entity_id)
assert state.state == expected
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass,
TEST_SWITCH,
style,
SWITCH_ACTIONS,
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_SWITCH,
ConfigurationStyle.MODERN,
0,
{
**SWITCH_ACTIONS,
"attributes": {"device_class": "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: device_class"
in caplog.text
)
+46
View File
@@ -6,6 +6,7 @@ import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components import template, update
from homeassistant.components.template.update import DEFAULT_NAME
from homeassistant.const import (
ATTR_ENTITY_PICTURE,
ATTR_ICON,
@@ -24,6 +25,7 @@ from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_action,
assert_extra_template_attributes,
async_get_flow_preview_state,
make_test_action,
make_test_trigger,
@@ -980,3 +982,47 @@ async def test_flow_preview(
assert state["state"] == STATE_ON
assert state["attributes"]["installed_version"] == "1.0"
assert state["attributes"]["latest_version"] == "2.0"
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass,
TEST_UPDATE,
style,
TEST_UPDATE_CONFIG,
)
@pytest.mark.parametrize(
"attribute",
[*list(update.UpdateEntityStateAttribute), "device_class"],
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_UPDATE,
style,
0,
{
**TEST_UPDATE_CONFIG,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)
+47
View File
@@ -7,6 +7,7 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components import template
from homeassistant.components.template.const import CONF_PICTURE, DOMAIN
from homeassistant.components.template.weather import DEFAULT_NAME
from homeassistant.components.weather import (
ATTR_WEATHER_APPARENT_TEMPERATURE,
ATTR_WEATHER_CLOUD_COVERAGE,
@@ -23,6 +24,7 @@ from homeassistant.components.weather import (
DOMAIN as WEATHER_DOMAIN,
SERVICE_GET_FORECASTS,
Forecast,
WeatherEntityStateAttribute,
)
from homeassistant.const import (
ATTR_ATTRIBUTION,
@@ -40,6 +42,7 @@ from homeassistant.setup import async_setup_component
from .conftest import (
ConfigurationStyle,
TemplatePlatformSetup,
assert_extra_template_attributes,
assert_state_and_attributes,
async_get_flow_preview_state,
async_trigger,
@@ -1027,3 +1030,47 @@ async def test_flow_preview(
)
assert state["state"] == "sunny"
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_extra_template_attributes(
hass: HomeAssistant, style: ConfigurationStyle
) -> None:
"""Test extra attributes."""
await assert_extra_template_attributes(
hass,
TEST_WEATHER,
style,
TEST_MODERN_REQUIRED,
)
@pytest.mark.parametrize(
"attribute",
list(WeatherEntityStateAttribute),
)
@pytest.mark.parametrize(
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
)
async def test_blocked_template_attributes(
hass: HomeAssistant,
style: ConfigurationStyle,
attribute,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test blocked extra attributes."""
await setup_entity(
hass,
TEST_WEATHER,
style,
0,
{
**TEST_MODERN_REQUIRED,
"attributes": {str(attribute): "{{ 'does not matter' }}"},
},
)
assert (
f"Unsupported attribute(s) found for {DEFAULT_NAME}: {attribute}" in caplog.text
)