From 16b42cc1099d7c1e48dda1cfab0224b2e08a081e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 29 Apr 2025 07:36:37 +0200 Subject: [PATCH] Add cv.renamed (#143834) --- homeassistant/components/automation/config.py | 31 ++----------------- homeassistant/helpers/config_validation.py | 25 +++++++++++++++ tests/helpers/test_config_validation.py | 27 ++++++++++++++++ 3 files changed, 55 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index fe74865ca92..c4425ce099a 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -58,34 +58,9 @@ _MINIMAL_PLATFORM_SCHEMA = vol.Schema( def _backward_compat_schema(value: Any | None) -> Any: """Backward compatibility for automations.""" - if not isinstance(value, dict): - return value - - # `trigger` has been renamed to `triggers` - if CONF_TRIGGER in value: - if CONF_TRIGGERS in value: - raise vol.Invalid( - "Cannot specify both 'trigger' and 'triggers'. Please use 'triggers' only." - ) - value[CONF_TRIGGERS] = value.pop(CONF_TRIGGER) - - # `condition` has been renamed to `conditions` - if CONF_CONDITION in value: - if CONF_CONDITIONS in value: - raise vol.Invalid( - "Cannot specify both 'condition' and 'conditions'. Please use 'conditions' only." - ) - value[CONF_CONDITIONS] = value.pop(CONF_CONDITION) - - # `action` has been renamed to `actions` - if CONF_ACTION in value: - if CONF_ACTIONS in value: - raise vol.Invalid( - "Cannot specify both 'action' and 'actions'. Please use 'actions' only." - ) - value[CONF_ACTIONS] = value.pop(CONF_ACTION) - - return value + value = cv.renamed(CONF_TRIGGER, CONF_TRIGGERS)(value) + value = cv.renamed(CONF_ACTION, CONF_ACTIONS)(value) + return cv.renamed(CONF_CONDITION, CONF_CONDITIONS)(value) PLATFORM_SCHEMA = vol.All( diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 655913558d6..0ce2c9e02e0 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1059,6 +1059,31 @@ def removed( ) +def renamed( + old_key: str, + new_key: str, +) -> Callable[[Any], Any]: + """Replace key with a new key. + + Fails if both the new and old key are present. + """ + + def validator(value: Any) -> Any: + if not isinstance(value, dict): + return value + + if old_key in value: + if new_key in value: + raise vol.Invalid( + f"Cannot specify both '{old_key}' and '{new_key}'. Please use '{new_key}' only." + ) + value[new_key] = value.pop(old_key) + + return value + + return validator + + def key_value_schemas( key: str, value_schemas: dict[Hashable, VolSchemaType | Callable[[Any], dict[str, Any]]], diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index c72295493e8..ecf5271dafd 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1953,3 +1953,30 @@ async def test_is_entity_service_schema( vol.All(vol.Schema(cv.make_entity_service_schema({"some": str}))), ): assert cv.is_entity_service_schema(schema) is True + + +def test_renamed(caplog: pytest.LogCaptureFixture, schema) -> None: + """Test renamed.""" + renamed_schema = vol.All(cv.renamed("mors", "mars"), schema) + + test_data = {"mars": True} + output = renamed_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert output == test_data + + test_data = {"mors": True} + output = renamed_schema(test_data.copy()) + assert len(caplog.records) == 0 + assert output == {"mars": True} + + test_data = {"mars": True, "mors": True} + with pytest.raises( + vol.Invalid, + match="Cannot specify both 'mors' and 'mars'. Please use 'mars' only.", + ): + renamed_schema(test_data.copy()) + assert len(caplog.records) == 0 + + # Check error handling if data is not a dict + with pytest.raises(vol.Invalid, match="expected a dictionary"): + renamed_schema([])