From 0ccabe9f2c9833ee34a5841702ea9667f3b84047 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 2 Jul 2025 06:41:39 +0000 Subject: [PATCH 1/2] Allow use of Selector in ObjectSelector fields --- homeassistant/helpers/selector.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index e4277aac98ecb..b3bdba39d9109 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Callable, Mapping, Sequence +from copy import deepcopy from enum import StrEnum from functools import cache import importlib @@ -1133,7 +1134,7 @@ class ObjectSelectorField(TypedDict): label: str required: bool - selector: dict[str, Any] + selector: Required[Selector | dict[str, Any]] class ObjectSelectorConfig(BaseSelectorConfig): @@ -1142,7 +1143,7 @@ class ObjectSelectorConfig(BaseSelectorConfig): fields: dict[str, ObjectSelectorField] multiple: bool label_field: str - description_field: bool + description_field: str translation_key: str @@ -1156,7 +1157,7 @@ class ObjectSelector(Selector[ObjectSelectorConfig]): { vol.Optional("fields"): { str: { - vol.Required("selector"): dict, + vol.Required("selector"): vol.Any(Selector, dict), vol.Optional("required"): bool, vol.Optional("label"): str, } @@ -1172,6 +1173,17 @@ def __init__(self, config: ObjectSelectorConfig | None = None) -> None: """Instantiate a selector.""" super().__init__(config) + def serialize(self) -> dict[str, dict[str, ObjectSelectorConfig]]: + """Serialize ObjectSelector for voluptuous_serialize.""" + _config = deepcopy(self.config) + if "fields" in _config: + for items in _config["fields"].values(): + if isinstance(items["selector"], Selector): + items["selector"] = { + items["selector"].selector_type: items["selector"].config + } + return {"selector": {self.selector_type: _config}} + def __call__(self, data: Any) -> Any: """Validate the passed selection.""" return data From 7c159b64a455e4d5ec763f22e4150ad66799dd7d Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 2 Jul 2025 07:30:58 +0000 Subject: [PATCH 2/2] Add test --- tests/helpers/snapshots/test_selector.ambr | 33 +++++++++++++++++++ tests/helpers/test_selector.py | 38 ++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 tests/helpers/snapshots/test_selector.ambr diff --git a/tests/helpers/snapshots/test_selector.ambr b/tests/helpers/snapshots/test_selector.ambr new file mode 100644 index 0000000000000..152230b2b0260 --- /dev/null +++ b/tests/helpers/snapshots/test_selector.ambr @@ -0,0 +1,33 @@ +# serializer version: 1 +# name: test_object_selector_uses_selectors + dict({ + 'selector': dict({ + 'object': dict({ + 'description_field': 'percentage', + 'fields': dict({ + 'name': dict({ + 'required': True, + 'selector': dict({ + 'text': dict({ + 'multiline': False, + 'multiple': False, + }), + }), + }), + 'percentage': dict({ + 'selector': dict({ + 'number': dict({ + 'max': 100.0, + 'min': 0.0, + 'mode': 'slider', + 'step': 1.0, + }), + }), + }), + }), + 'label_field': 'name', + 'multiple': True, + }), + }), + }) +# --- diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 8947ea8099cd8..5c672edf9370a 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -4,6 +4,7 @@ from typing import Any import pytest +from syrupy.assertion import SnapshotAssertion import voluptuous as vol from homeassistant.helpers import selector @@ -618,6 +619,43 @@ def test_object_selector_schema(schema, valid_selections, invalid_selections) -> _test_selector("object", schema, valid_selections, invalid_selections) +def test_object_selector_uses_selectors(snapshot: SnapshotAssertion) -> None: + """Test ObjectSelector custom serializer for using Selector in ObjectSelectorField.""" + + selector_type = "object" + schema = { + "fields": { + "name": { + "required": True, + "selector": selector.TextSelector(), + }, + "percentage": { + "selector": selector.NumberSelector( + selector.NumberSelectorConfig(min=0, max=100) + ), + }, + }, + "multiple": True, + "label_field": "name", + "description_field": "percentage", + } + + # Validate selector configuration + config = {selector_type: schema} + selector.validate_selector(config) + selector_instance = selector.selector(config) + + # Serialize selector + selector_instance = selector.selector({selector_type: schema}) + assert selector_instance.serialize() != { + "selector": {selector_type: selector_instance.config} + } + assert selector_instance.serialize() == snapshot() + + # Test serialized selector can be dumped to YAML + yaml_util.dump(selector_instance.serialize()) + + @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), [