mirror of
https://github.com/home-assistant/core.git
synced 2026-04-20 08:29:39 +02:00
Fix template migration errors (#157949)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Helpers for template integration."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
import hashlib
|
||||
import itertools
|
||||
import logging
|
||||
@@ -33,6 +33,7 @@ from homeassistant.helpers.entity_platform import (
|
||||
async_get_platforms,
|
||||
)
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity
|
||||
from homeassistant.helpers.script_variables import ScriptVariables
|
||||
from homeassistant.helpers.singleton import singleton
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import yaml as yaml_util
|
||||
@@ -190,12 +191,12 @@ def async_create_template_tracking_entities(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
def _format_template(value: Any) -> Any:
|
||||
def _format_template(value: Any, field: str | None = None) -> Any:
|
||||
if isinstance(value, template.Template):
|
||||
return value.template
|
||||
|
||||
if isinstance(value, Enum):
|
||||
return value.name
|
||||
if isinstance(value, StrEnum):
|
||||
return value.value
|
||||
|
||||
if isinstance(value, (int, float, str, bool)):
|
||||
return value
|
||||
@@ -207,14 +208,13 @@ def format_migration_config(
|
||||
config: ConfigType | list[ConfigType], depth: int = 0
|
||||
) -> ConfigType | list[ConfigType]:
|
||||
"""Recursive method to format templates as strings from ConfigType."""
|
||||
types = (dict, list)
|
||||
if depth > 9:
|
||||
raise RecursionError
|
||||
|
||||
if isinstance(config, list):
|
||||
items = []
|
||||
for item in config:
|
||||
if isinstance(item, types):
|
||||
if isinstance(item, (dict, list)):
|
||||
if len(item) > 0:
|
||||
items.append(format_migration_config(item, depth + 1))
|
||||
else:
|
||||
@@ -223,9 +223,18 @@ def format_migration_config(
|
||||
|
||||
formatted_config = {}
|
||||
for field, value in config.items():
|
||||
if isinstance(value, types):
|
||||
if isinstance(value, dict):
|
||||
if len(value) > 0:
|
||||
formatted_config[field] = format_migration_config(value, depth + 1)
|
||||
elif isinstance(value, list):
|
||||
if len(value) > 0:
|
||||
formatted_config[field] = format_migration_config(value, depth + 1)
|
||||
else:
|
||||
formatted_config[field] = []
|
||||
elif isinstance(value, ScriptVariables):
|
||||
formatted_config[field] = format_migration_config(
|
||||
value.as_dict(), depth + 1
|
||||
)
|
||||
else:
|
||||
formatted_config[field] = _format_template(value)
|
||||
|
||||
|
||||
@@ -600,6 +600,270 @@ async def test_legacy_deprecation(
|
||||
assert "platform: template" not in issue.translation_placeholders["config"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("domain", "config", "strings_to_check"),
|
||||
[
|
||||
(
|
||||
"light",
|
||||
{
|
||||
"light": {
|
||||
"platform": "template",
|
||||
"lights": {
|
||||
"garage_light_template": {
|
||||
"friendly_name": "Garage Light Template",
|
||||
"min_mireds_template": 153,
|
||||
"max_mireds_template": 500,
|
||||
"turn_on": [],
|
||||
"turn_off": [],
|
||||
"set_temperature": [],
|
||||
"set_hs": [],
|
||||
"set_level": [],
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
[
|
||||
"turn_on: []",
|
||||
"turn_off: []",
|
||||
"set_temperature: []",
|
||||
"set_hs: []",
|
||||
"set_level: []",
|
||||
],
|
||||
),
|
||||
(
|
||||
"switch",
|
||||
{
|
||||
"switch": {
|
||||
"platform": "template",
|
||||
"switches": {
|
||||
"my_switch": {
|
||||
"friendly_name": "Switch Template",
|
||||
"turn_on": [],
|
||||
"turn_off": [],
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
[
|
||||
"turn_on: []",
|
||||
"turn_off: []",
|
||||
],
|
||||
),
|
||||
(
|
||||
"light",
|
||||
{
|
||||
"light": [
|
||||
{
|
||||
"platform": "template",
|
||||
"lights": {
|
||||
"atrium_lichterkette": {
|
||||
"unique_id": "atrium_lichterkette",
|
||||
"friendly_name": "Atrium Lichterkette",
|
||||
"value_template": "{{ states('input_boolean.atrium_lichterkette_power') }}",
|
||||
"level_template": "{% if is_state('input_boolean.atrium_lichterkette_power', 'off') %}\n 0\n{% else %}\n {{ states('input_number.atrium_lichterkette_brightness') | int * (255 / state_attr('input_number.atrium_lichterkette_brightness', 'max') | int) }}\n{% endif %}",
|
||||
"effect_list_template": "{{ state_attr('input_select.atrium_lichterkette_mode', 'options') }}",
|
||||
"effect_template": "'{{ states('input_select.atrium_lichterkette_mode')}}'",
|
||||
"turn_on": [
|
||||
{
|
||||
"service": "button.press",
|
||||
"target": {
|
||||
"entity_id": "button.esphome_web_28a814_lichterkette_on"
|
||||
},
|
||||
},
|
||||
{
|
||||
"service": "input_boolean.turn_on",
|
||||
"target": {
|
||||
"entity_id": "input_boolean.atrium_lichterkette_power"
|
||||
},
|
||||
},
|
||||
],
|
||||
"turn_off": [
|
||||
{
|
||||
"service": "button.press",
|
||||
"target": {
|
||||
"entity_id": "button.esphome_web_28a814_lichterkette_off"
|
||||
},
|
||||
},
|
||||
{
|
||||
"service": "input_boolean.turn_off",
|
||||
"target": {
|
||||
"entity_id": "input_boolean.atrium_lichterkette_power"
|
||||
},
|
||||
},
|
||||
],
|
||||
"set_level": [
|
||||
{
|
||||
"variables": {
|
||||
"scaled": "{{ (brightness / (255 / state_attr('input_number.atrium_lichterkette_brightness', 'max'))) | round | int }}",
|
||||
"diff": "{{ scaled | int - states('input_number.atrium_lichterkette_brightness') | int }}",
|
||||
"direction": "{{ 'dim' if diff | int < 0 else 'bright' }}",
|
||||
}
|
||||
},
|
||||
{
|
||||
"repeat": {
|
||||
"count": "{{ diff | int | abs }}",
|
||||
"sequence": [
|
||||
{
|
||||
"service": "button.press",
|
||||
"target": {
|
||||
"entity_id": "button.esphome_web_28a814_lichterkette_{{ direction }}"
|
||||
},
|
||||
},
|
||||
{"delay": {"milliseconds": 500}},
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
"service": "input_number.set_value",
|
||||
"data": {
|
||||
"value": "{{ scaled }}",
|
||||
"entity_id": "input_number.atrium_lichterkette_brightness",
|
||||
},
|
||||
},
|
||||
],
|
||||
"set_effect": [
|
||||
{
|
||||
"service": "button.press",
|
||||
"target": {
|
||||
"entity_id": "button.esphome_web_28a814_lichterkette_{{ effect }}"
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
[
|
||||
"scaled: ",
|
||||
"diff: ",
|
||||
"direction: ",
|
||||
],
|
||||
),
|
||||
(
|
||||
"cover",
|
||||
{
|
||||
"cover": [
|
||||
{
|
||||
"platform": "template",
|
||||
"covers": {
|
||||
"large_garage_door": {
|
||||
"device_class": "garage",
|
||||
"friendly_name": "Large Garage Door",
|
||||
"value_template": "{% if is_state('binary_sensor.large_garage_door', 'off') %}\n closed\n{% elif is_state('timer.large_garage_opening_timer', 'active') %}\n opening\n{% elif is_state('timer.large_garage_closing_timer', 'active') %} \n closing\n{% elif is_state('binary_sensor.large_garage_door', 'on') %}\n open\n{% endif %}\n",
|
||||
"open_cover": [
|
||||
{
|
||||
"condition": "state",
|
||||
"entity_id": "binary_sensor.large_garage_door",
|
||||
"state": "off",
|
||||
},
|
||||
{
|
||||
"action": "switch.turn_on",
|
||||
"target": {
|
||||
"entity_id": "switch.garage_door_relay_1"
|
||||
},
|
||||
},
|
||||
{
|
||||
"action": "timer.start",
|
||||
"entity_id": "timer.large_garage_opening_timer",
|
||||
},
|
||||
],
|
||||
"close_cover": [
|
||||
{
|
||||
"condition": "state",
|
||||
"entity_id": "binary_sensor.large_garage_door",
|
||||
"state": "on",
|
||||
},
|
||||
{
|
||||
"action": "switch.turn_on",
|
||||
"target": {
|
||||
"entity_id": "switch.garage_door_relay_1"
|
||||
},
|
||||
},
|
||||
{
|
||||
"action": "timer.start",
|
||||
"entity_id": "timer.large_garage_closing_timer",
|
||||
},
|
||||
],
|
||||
"stop_cover": [
|
||||
{
|
||||
"action": "switch.turn_on",
|
||||
"target": {
|
||||
"entity_id": "switch.garage_door_relay_1"
|
||||
},
|
||||
},
|
||||
{
|
||||
"action": "timer.cancel",
|
||||
"entity_id": "timer.large_garage_opening_timer",
|
||||
},
|
||||
{
|
||||
"action": "timer.cancel",
|
||||
"entity_id": "timer.large_garage_closing_timer",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
["device_class: garage"],
|
||||
),
|
||||
(
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "template",
|
||||
"sensors": {
|
||||
"motion_sensor": {
|
||||
"friendly_name": "Motion Sensor",
|
||||
"device_class": "motion",
|
||||
"value_template": "{{ is_state('sensor.motion_detector', 'on') }}",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
["device_class: motion"],
|
||||
),
|
||||
(
|
||||
"sensor",
|
||||
{
|
||||
"sensor": {
|
||||
"platform": "template",
|
||||
"sensors": {
|
||||
"some_sensor": {
|
||||
"friendly_name": "Sensor",
|
||||
"device_class": "timestamp",
|
||||
"value_template": "{{ now().isoformat() }}",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
["device_class: timestamp"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_legacy_deprecation_with_unique_objects(
|
||||
hass: HomeAssistant,
|
||||
domain: str,
|
||||
config: dict,
|
||||
strings_to_check: list[str],
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test legacy configuration raises issue and unique objects are properly converted to valid configurations."""
|
||||
|
||||
await async_setup_component(hass, domain, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(issue_registry.issues) == 1
|
||||
issue = next(iter(issue_registry.issues.values()))
|
||||
|
||||
assert issue.domain == "template"
|
||||
assert issue.severity == ir.IssueSeverity.WARNING
|
||||
assert issue.translation_placeholders is not None
|
||||
for string in strings_to_check:
|
||||
assert string in issue.translation_placeholders["config"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("domain", "config"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user