diff --git a/homeassistant/components/ai_task/services.yaml b/homeassistant/components/ai_task/services.yaml index 194c0e07bc3..feefa70a30b 100644 --- a/homeassistant/components/ai_task/services.yaml +++ b/homeassistant/components/ai_task/services.yaml @@ -15,9 +15,10 @@ generate_data: required: false selector: entity: - domain: ai_task - supported_features: - - ai_task.AITaskEntityFeature.GENERATE_DATA + filter: + domain: ai_task + supported_features: + - ai_task.AITaskEntityFeature.GENERATE_DATA structure: advanced: true required: false diff --git a/homeassistant/components/assist_satellite/services.yaml b/homeassistant/components/assist_satellite/services.yaml index 8433eb6102d..ed292e1626c 100644 --- a/homeassistant/components/assist_satellite/services.yaml +++ b/homeassistant/components/assist_satellite/services.yaml @@ -68,9 +68,10 @@ ask_question: required: true selector: entity: - domain: assist_satellite - supported_features: - - assist_satellite.AssistSatelliteEntityFeature.START_CONVERSATION + filter: + domain: assist_satellite + supported_features: + - assist_satellite.AssistSatelliteEntityFeature.START_CONVERSATION question: required: false example: "What kind of music would you like to play?" diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index bc24113251c..83524fac24c 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -160,6 +160,22 @@ ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA = vol.Schema( ) +# Legacy entity selector config schema used directly under entity selectors +# is provided for backwards compatibility and remains feature frozen. +# New filtering features should be added under the `filter` key instead. +# https://github.com/home-assistant/frontend/pull/15302 +LEGACY_ENTITY_SELECTOR_CONFIG_SCHEMA = vol.Schema( + { + # Integration that provided the entity + vol.Optional("integration"): str, + # Domain the entity belongs to + vol.Optional("domain"): vol.All(cv.ensure_list, [str]), + # Device class of the entity + vol.Optional("device_class"): vol.All(cv.ensure_list, [str]), + } +) + + class EntityFilterSelectorConfig(TypedDict, total=False): """Class to represent a single entity selector config.""" @@ -179,10 +195,22 @@ DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA = vol.Schema( vol.Optional("model"): str, # Model ID of device vol.Optional("model_id"): str, - # Device has to contain entities matching this selector - vol.Optional("entity"): vol.All( - cv.ensure_list, [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA] - ), + } +) + + +# Legacy device selector config schema used directly under device selectors +# is provided for backwards compatibility and remains feature frozen. +# New filtering features should be added under the `filter` key instead. +# https://github.com/home-assistant/frontend/pull/15302 +LEGACY_DEVICE_SELECTOR_CONFIG_SCHEMA = vol.Schema( + { + # Integration linked to it with a config entry + vol.Optional("integration"): str, + # Manufacturer of device + vol.Optional("manufacturer"): str, + # Model of device + vol.Optional("model"): str, } ) @@ -714,9 +742,13 @@ class DeviceSelector(Selector[DeviceSelectorConfig]): selector_type = "device" CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( - DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA.schema + LEGACY_DEVICE_SELECTOR_CONFIG_SCHEMA.schema ).extend( { + # Device has to contain entities matching this selector + vol.Optional("entity"): vol.All( + cv.ensure_list, [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA] + ), vol.Optional("multiple", default=False): cv.boolean, vol.Optional("filter"): vol.All( cv.ensure_list, @@ -794,7 +826,7 @@ class EntitySelector(Selector[EntitySelectorConfig]): selector_type = "entity" CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( - ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA.schema + LEGACY_ENTITY_SELECTOR_CONFIG_SCHEMA.schema ).extend( { vol.Optional("exclude_entities"): [str], diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 0e68992d0e4..159f295ab2f 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -88,7 +88,6 @@ def _test_selector( ({"integration": "zha"}, ("abc123",), (None,)), ({"manufacturer": "mock-manuf"}, ("abc123",), (None,)), ({"model": "mock-model"}, ("abc123",), (None,)), - ({"model_id": "mock-model_id"}, ("abc123",), (None,)), ({"manufacturer": "mock-manuf", "model": "mock-model"}, ("abc123",), (None,)), ( {"integration": "zha", "manufacturer": "mock-manuf", "model": "mock-model"}, @@ -128,6 +127,7 @@ def _test_selector( "integration": "zha", "manufacturer": "mock-manuf", "model": "mock-model", + "model_id": "mock-model_id", } }, ("abc123",), @@ -140,11 +140,13 @@ def _test_selector( "integration": "zha", "manufacturer": "mock-manuf", "model": "mock-model", + "model_id": "mock-model_id", }, { "integration": "matter", "manufacturer": "other-mock-manuf", "model": "other-mock-model", + "model_id": "other-mock-model_id", }, ] }, @@ -158,6 +160,19 @@ def test_device_selector_schema(schema, valid_selections, invalid_selections) -> _test_selector("device", schema, valid_selections, invalid_selections) +@pytest.mark.parametrize( + "schema", + [ + # model_id should be used under the filter key + {"model_id": "mock-model_id"}, + ], +) +def test_device_selector_schema_error(schema) -> None: + """Test device selector.""" + with pytest.raises(vol.Invalid): + selector.validate_selector({"device": schema}) + + @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), [ @@ -290,10 +305,12 @@ def test_entity_selector_schema(schema, valid_selections, invalid_selections) -> {"filter": [{"supported_features": ["light.FooEntityFeature.blah"]}]}, # Unknown feature enum member {"filter": [{"supported_features": ["light.LightEntityFeature.blah"]}]}, + # supported_features should be used under the filter key + {"supported_features": ["light.LightEntityFeature.EFFECT"]}, ], ) def test_entity_selector_schema_error(schema) -> None: - """Test number selector.""" + """Test entity selector.""" with pytest.raises(vol.Invalid): selector.validate_selector({"entity": schema})