From c5b4892596b46ae4e009e8c98cc7190b891317a9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 27 Sep 2024 19:08:12 +0200 Subject: [PATCH] Adjust BaseEditConfigView.__init__ (#126729) --- homeassistant/components/config/automation.py | 6 +-- homeassistant/components/config/scene.py | 2 +- homeassistant/components/config/script.py | 6 +-- homeassistant/components/config/view.py | 11 ++++- tests/components/config/test_view.py | 41 +++++++++++++++++++ 5 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 tests/components/config/test_view.py diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 54af1df8c54..f2646aa5451 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -6,10 +6,7 @@ from typing import Any import uuid from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN -from homeassistant.components.automation.config import ( - PLATFORM_SCHEMA, - async_validate_config_item, -) +from homeassistant.components.automation.config import async_validate_config_item from homeassistant.config import AUTOMATION_CONFIG_PATH from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.core import HomeAssistant, callback @@ -48,7 +45,6 @@ def async_setup(hass: HomeAssistant) -> bool: "config", AUTOMATION_CONFIG_PATH, cv.string, - PLATFORM_SCHEMA, post_write_hook=hook, data_validator=async_validate_config_item, ) diff --git a/homeassistant/components/config/scene.py b/homeassistant/components/config/scene.py index e33942e9986..2f0fc180c0b 100644 --- a/homeassistant/components/config/scene.py +++ b/homeassistant/components/config/scene.py @@ -47,7 +47,7 @@ def async_setup(hass: HomeAssistant) -> bool: "config", SCENE_CONFIG_PATH, cv.string, - PLATFORM_SCHEMA, + data_schema=PLATFORM_SCHEMA, post_write_hook=hook, ) ) diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index c6aabc5bc54..aa83329d124 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -5,10 +5,7 @@ from __future__ import annotations from typing import Any from homeassistant.components.script import DOMAIN as SCRIPT_DOMAIN -from homeassistant.components.script.config import ( - SCRIPT_ENTITY_SCHEMA, - async_validate_config_item, -) +from homeassistant.components.script.config import async_validate_config_item from homeassistant.config import SCRIPT_CONFIG_PATH from homeassistant.const import SERVICE_RELOAD from homeassistant.core import HomeAssistant, callback @@ -45,7 +42,6 @@ def async_setup(hass: HomeAssistant) -> bool: "config", SCRIPT_CONFIG_PATH, cv.slug, - SCRIPT_ENTITY_SCHEMA, post_write_hook=hook, data_validator=async_validate_config_item, ) diff --git a/homeassistant/components/config/view.py b/homeassistant/components/config/view.py index 980c0f82dd1..14d89356c92 100644 --- a/homeassistant/components/config/view.py +++ b/homeassistant/components/config/view.py @@ -33,9 +33,9 @@ class BaseEditConfigView[_DataT: (dict[str, dict[str, Any]], list[dict[str, Any] config_type: str, path: str, key_schema: Callable[[Any], str], - data_schema: Callable[[dict[str, Any]], Any], *, post_write_hook: Callable[[str, str], Coroutine[Any, Any, None]] | None = None, + data_schema: Callable[[dict[str, Any]], Any] | None = None, data_validator: Callable[ [HomeAssistant, str, dict[str, Any]], Coroutine[Any, Any, dict[str, Any] | None], @@ -51,6 +51,12 @@ class BaseEditConfigView[_DataT: (dict[str, dict[str, Any]], list[dict[str, Any] self.post_write_hook = post_write_hook self.data_validator = data_validator self.mutation_lock = asyncio.Lock() + if (self.data_schema is None and self.data_validator is None) or ( + self.data_schema is not None and self.data_validator is not None + ): + raise ValueError( + "Must specify exactly one of data_schema or data_validator" + ) def _empty_config(self) -> _DataT: """Empty config if file not found.""" @@ -112,7 +118,8 @@ class BaseEditConfigView[_DataT: (dict[str, dict[str, Any]], list[dict[str, Any] if self.data_validator: await self.data_validator(hass, config_key, data) else: - self.data_schema(data) + # We either have a data_schema or a data_validator, ignore mypy + self.data_schema(data) # type: ignore[misc] except (vol.Invalid, HomeAssistantError) as err: return self.json_message( f"Message malformed: {err}", HTTPStatus.BAD_REQUEST diff --git a/tests/components/config/test_view.py b/tests/components/config/test_view.py new file mode 100644 index 00000000000..0bea9240a89 --- /dev/null +++ b/tests/components/config/test_view.py @@ -0,0 +1,41 @@ +"""Test config HTTP views.""" + +from collections.abc import Callable +from contextlib import AbstractContextManager, nullcontext as does_not_raise + +import pytest + +from homeassistant.components.config import view +from homeassistant.core import HomeAssistant + + +async def _mock_validator(hass: HomeAssistant, key: str, data: dict) -> dict: + """Mock data validator.""" + return data + + +@pytest.mark.parametrize( + ("data_schema", "data_validator", "expected_result"), + [ + (None, None, pytest.raises(ValueError)), + (None, _mock_validator, does_not_raise()), + (lambda x: x, None, does_not_raise()), + (lambda x: x, _mock_validator, pytest.raises(ValueError)), + ], +) +async def test_view_requires_data_schema_or_validator( + hass: HomeAssistant, + data_schema: Callable | None, + data_validator: Callable | None, + expected_result: AbstractContextManager, +) -> None: + """Test the view base class requires a schema or validator.""" + with expected_result: + view.BaseEditConfigView( + "test", + "test", + "test", + lambda x: "", + data_schema=data_schema, + data_validator=data_validator, + )