mirror of
https://github.com/home-assistant/core.git
synced 2025-12-24 21:06:19 +00:00
Ensure backwards compatibility for new-style configs in old triggers and conditions (#156446)
This commit is contained in:
@@ -34,7 +34,8 @@ def move_top_level_schema_fields_to_options(
|
||||
) -> ConfigType:
|
||||
"""Move top-level fields to options.
|
||||
|
||||
This function is used to help migrating old-style configs to new-style configs.
|
||||
This function is used to help migrating old-style configs to new-style configs
|
||||
for triggers and conditions.
|
||||
If options is already present, the config is returned as-is.
|
||||
"""
|
||||
if CONF_OPTIONS in config:
|
||||
@@ -50,3 +51,38 @@ def move_top_level_schema_fields_to_options(
|
||||
options[key] = config.pop(key)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def move_options_fields_to_top_level(
|
||||
config: ConfigType, base_schema: vol.Schema
|
||||
) -> ConfigType:
|
||||
"""Move options fields to top-level.
|
||||
|
||||
This function is used to provide backwards compatibility for new-style configs
|
||||
for triggers and conditions.
|
||||
|
||||
The config is returned as-is, if any of the following is true:
|
||||
- options is not present
|
||||
- options is not a dict
|
||||
- the config with options field removed fails the base_schema validation (most
|
||||
likely due to additional keys being present)
|
||||
|
||||
Those conditions are checked to make it so that only configs that have the structure
|
||||
of the new-style are modified, whereas valid old-style configs are preserved.
|
||||
"""
|
||||
options = config.get(CONF_OPTIONS)
|
||||
|
||||
if not isinstance(options, dict):
|
||||
return config
|
||||
|
||||
new_config: ConfigType = config.copy()
|
||||
new_config.pop(CONF_OPTIONS)
|
||||
|
||||
try:
|
||||
new_config = base_schema(new_config)
|
||||
except vol.Invalid:
|
||||
return config
|
||||
|
||||
new_config.update(options)
|
||||
|
||||
return new_config
|
||||
|
||||
@@ -64,7 +64,11 @@ from homeassistant.util.hass_dict import HassKey
|
||||
from homeassistant.util.yaml import load_yaml_dict
|
||||
|
||||
from . import config_validation as cv, entity_registry as er, selector
|
||||
from .automation import get_absolute_description_key, get_relative_description_key
|
||||
from .automation import (
|
||||
get_absolute_description_key,
|
||||
get_relative_description_key,
|
||||
move_options_fields_to_top_level,
|
||||
)
|
||||
from .integration_platform import async_process_integration_platforms
|
||||
from .selector import TargetSelector
|
||||
from .template import Template, render_complex
|
||||
@@ -202,10 +206,14 @@ async def _register_condition_platform(
|
||||
_LOGGER.exception("Error while notifying condition platform listener")
|
||||
|
||||
|
||||
_CONDITION_SCHEMA = vol.Schema(
|
||||
_CONDITION_BASE_SCHEMA = vol.Schema(
|
||||
{
|
||||
**cv.CONDITION_BASE_SCHEMA,
|
||||
vol.Required(CONF_CONDITION): str,
|
||||
}
|
||||
)
|
||||
_CONDITION_SCHEMA = _CONDITION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_OPTIONS): object,
|
||||
vol.Optional(CONF_TARGET): cv.TARGET_FIELDS,
|
||||
}
|
||||
@@ -1044,6 +1052,8 @@ async def async_validate_condition_config(
|
||||
raise vol.Invalid(f"Invalid condition '{condition_key}' specified")
|
||||
return await condition_class.async_validate_complete_config(hass, config)
|
||||
|
||||
config = move_options_fields_to_top_level(config, _CONDITION_BASE_SCHEMA)
|
||||
|
||||
if condition_key in ("numeric_state", "state"):
|
||||
validator = cast(
|
||||
Callable[[HomeAssistant, ConfigType], ConfigType],
|
||||
|
||||
@@ -46,7 +46,11 @@ from homeassistant.util.hass_dict import HassKey
|
||||
from homeassistant.util.yaml import load_yaml_dict
|
||||
|
||||
from . import config_validation as cv, selector
|
||||
from .automation import get_absolute_description_key, get_relative_description_key
|
||||
from .automation import (
|
||||
get_absolute_description_key,
|
||||
get_relative_description_key,
|
||||
move_options_fields_to_top_level,
|
||||
)
|
||||
from .integration_platform import async_process_integration_platforms
|
||||
from .selector import TargetSelector
|
||||
from .template import Template
|
||||
@@ -497,8 +501,10 @@ async def async_validate_trigger_config(
|
||||
raise vol.Invalid(f"Invalid trigger '{trigger_key}' specified")
|
||||
conf = await trigger.async_validate_complete_config(hass, conf)
|
||||
elif hasattr(platform, "async_validate_trigger_config"):
|
||||
conf = move_options_fields_to_top_level(conf, cv.TRIGGER_BASE_SCHEMA)
|
||||
conf = await platform.async_validate_trigger_config(hass, conf)
|
||||
else:
|
||||
conf = move_options_fields_to_top_level(conf, cv.TRIGGER_BASE_SCHEMA)
|
||||
conf = platform.TRIGGER_SCHEMA(conf)
|
||||
config.append(conf)
|
||||
return config
|
||||
|
||||
@@ -6,6 +6,7 @@ import voluptuous as vol
|
||||
from homeassistant.helpers.automation import (
|
||||
get_absolute_description_key,
|
||||
get_relative_description_key,
|
||||
move_options_fields_to_top_level,
|
||||
move_top_level_schema_fields_to_options,
|
||||
)
|
||||
|
||||
@@ -106,3 +107,76 @@ async def test_move_schema_fields_to_options(
|
||||
assert (
|
||||
move_top_level_schema_fields_to_options(config, schema_dict) == expected_config
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "expected_config"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"platform": "test",
|
||||
"options": {
|
||||
"entity": "sensor.test",
|
||||
"from": "open",
|
||||
"to": "closed",
|
||||
"for": {"hours": 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
"platform": "test",
|
||||
"entity": "sensor.test",
|
||||
"from": "open",
|
||||
"to": "closed",
|
||||
"for": {"hours": 1},
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"platform": "test",
|
||||
"entity": "sensor.test",
|
||||
"from": "open",
|
||||
"to": "closed",
|
||||
"for": {"hours": 1},
|
||||
},
|
||||
{
|
||||
"platform": "test",
|
||||
"entity": "sensor.test",
|
||||
"from": "open",
|
||||
"to": "closed",
|
||||
"for": {"hours": 1},
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"platform": "test",
|
||||
"options": 456,
|
||||
},
|
||||
{
|
||||
"platform": "test",
|
||||
"options": 456,
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"platform": "test",
|
||||
"options": {
|
||||
"entity": "sensor.test",
|
||||
},
|
||||
"extra_field": "extra_value",
|
||||
},
|
||||
{
|
||||
"platform": "test",
|
||||
"options": {
|
||||
"entity": "sensor.test",
|
||||
},
|
||||
"extra_field": "extra_value",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_move_options_fields_to_top_level(config, expected_config) -> None:
|
||||
"""Test moving options fields to top-level."""
|
||||
base_schema = vol.Schema({vol.Required("platform"): str})
|
||||
original_config = config.copy()
|
||||
assert move_options_fields_to_top_level(config, base_schema) == expected_config
|
||||
assert config == original_config # Ensure original config is not modified
|
||||
|
||||
@@ -2170,7 +2170,7 @@ async def test_platform_multiple_conditions(hass: HomeAssistant) -> None:
|
||||
await condition.async_from_config(hass, config_3)
|
||||
|
||||
|
||||
async def test_platform_migrate_trigger(hass: HomeAssistant) -> None:
|
||||
async def test_platform_migrate_condition(hass: HomeAssistant) -> None:
|
||||
"""Test a condition platform with a migration."""
|
||||
|
||||
OPTIONS_SCHEMA_DICT = {
|
||||
@@ -2238,6 +2238,29 @@ async def test_platform_migrate_trigger(hass: HomeAssistant) -> None:
|
||||
)
|
||||
|
||||
|
||||
async def test_platform_backwards_compatibility_for_new_style_configs(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test backwards compatibility for old-style conditions with new-style configs."""
|
||||
config_old_style = {
|
||||
"condition": "numeric_state",
|
||||
"entity_id": ["sensor.test"],
|
||||
"above": 50,
|
||||
}
|
||||
result = await async_validate_condition_config(hass, config_old_style)
|
||||
assert result == config_old_style
|
||||
|
||||
config_new_style = {
|
||||
"condition": "numeric_state",
|
||||
"options": {
|
||||
"entity_id": ["sensor.test"],
|
||||
"above": 50,
|
||||
},
|
||||
}
|
||||
result = await async_validate_condition_config(hass, config_new_style)
|
||||
assert result == config_old_style
|
||||
|
||||
|
||||
@pytest.mark.parametrize("enabled_value", [True, "{{ 1 == 1 }}"])
|
||||
async def test_enabled_condition(
|
||||
hass: HomeAssistant, enabled_value: bool | str
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.core import (
|
||||
callback,
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import trigger
|
||||
from homeassistant.helpers import config_validation as cv, trigger
|
||||
from homeassistant.helpers.automation import move_top_level_schema_fields_to_options
|
||||
from homeassistant.helpers.trigger import (
|
||||
DATA_PLUGGABLE_ACTIONS,
|
||||
@@ -607,6 +607,35 @@ async def test_platform_migrate_trigger(hass: HomeAssistant) -> None:
|
||||
assert await async_validate_trigger_config(hass, config_4) == config_4
|
||||
|
||||
|
||||
async def test_platform_backwards_compatibility_for_new_style_configs(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test backwards compatibility for old-style triggers with new-style configs."""
|
||||
|
||||
class MockTriggerPlatform:
|
||||
"""Mock trigger platform."""
|
||||
|
||||
TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required("option_1"): str,
|
||||
vol.Optional("option_2"): int,
|
||||
}
|
||||
)
|
||||
|
||||
mock_integration(hass, MockModule("test"))
|
||||
mock_platform(hass, "test.trigger", MockTriggerPlatform())
|
||||
|
||||
config_old_style = [{"platform": "test", "option_1": "value_1", "option_2": 2}]
|
||||
result = await async_validate_trigger_config(hass, config_old_style)
|
||||
assert result == config_old_style
|
||||
|
||||
config_new_style = [
|
||||
{"platform": "test", "options": {"option_1": "value_1", "option_2": 2}}
|
||||
]
|
||||
result = await async_validate_trigger_config(hass, config_new_style)
|
||||
assert result == config_old_style
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sun_trigger_descriptions",
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user