mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Add multiple selection to media selector (#154350)
This commit is contained in:
@@ -30,6 +30,7 @@ generate_data:
|
||||
media:
|
||||
accept:
|
||||
- "*"
|
||||
multiple: true
|
||||
generate_image:
|
||||
fields:
|
||||
task_name:
|
||||
@@ -57,3 +58,4 @@ generate_image:
|
||||
media:
|
||||
accept:
|
||||
- "*"
|
||||
multiple: true
|
||||
|
||||
@@ -782,9 +782,19 @@ def selector_serializer(schema: Any) -> Any: # noqa: C901
|
||||
return {"type": "string", "enum": schema.config["languages"]}
|
||||
return {"type": "string", "format": "RFC 5646"}
|
||||
|
||||
if isinstance(schema, (selector.LocationSelector, selector.MediaSelector)):
|
||||
if isinstance(schema, selector.LocationSelector):
|
||||
return convert(schema.DATA_SCHEMA)
|
||||
|
||||
if isinstance(schema, selector.MediaSelector):
|
||||
item_schema = convert(schema.DATA_SCHEMA)
|
||||
# Media selector allows multiple when configured
|
||||
if schema.config.get("multiple"):
|
||||
return {
|
||||
"type": "array",
|
||||
"items": item_schema,
|
||||
}
|
||||
return item_schema
|
||||
|
||||
if isinstance(schema, selector.NumberSelector):
|
||||
result = {"type": "number"}
|
||||
if "min" in schema.config:
|
||||
|
||||
@@ -1030,6 +1030,7 @@ class MediaSelectorConfig(BaseSelectorConfig, total=False):
|
||||
"""Class to represent a media selector config."""
|
||||
|
||||
accept: list[str]
|
||||
multiple: bool
|
||||
|
||||
|
||||
@SELECTORS.register("media")
|
||||
@@ -1041,6 +1042,7 @@ class MediaSelector(Selector[MediaSelectorConfig]):
|
||||
CONFIG_SCHEMA = make_selector_config_schema(
|
||||
{
|
||||
vol.Optional("accept"): [str],
|
||||
vol.Optional("multiple", default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
@@ -1059,9 +1061,9 @@ class MediaSelector(Selector[MediaSelectorConfig]):
|
||||
"""Instantiate a selector."""
|
||||
super().__init__(config)
|
||||
|
||||
def __call__(self, data: Any) -> dict[str, str]:
|
||||
def __call__(self, data: Any) -> dict[str, str] | list[dict[str, str]]:
|
||||
"""Validate the passed selection."""
|
||||
schema = {
|
||||
item_schema_dict = {
|
||||
key: value
|
||||
for key, value in self.DATA_SCHEMA.schema.items()
|
||||
if key != "entity_id"
|
||||
@@ -1069,10 +1071,19 @@ class MediaSelector(Selector[MediaSelectorConfig]):
|
||||
|
||||
if "accept" not in self.config:
|
||||
# If accept is not set, the entity_id field is required
|
||||
schema[vol.Required("entity_id")] = cv.entity_id_or_uuid
|
||||
item_schema_dict[vol.Required("entity_id")] = cv.entity_id_or_uuid
|
||||
|
||||
media: dict[str, str] = vol.Schema(schema)(data)
|
||||
return media
|
||||
item_schema = vol.Schema(item_schema_dict)
|
||||
|
||||
if not self.config["multiple"]:
|
||||
media: dict[str, str] = item_schema(data)
|
||||
return media
|
||||
|
||||
# Backwards compatibility for places that now accept multiple items
|
||||
if not isinstance(data, list):
|
||||
data = [data]
|
||||
|
||||
return [item_schema(item) for item in data]
|
||||
|
||||
|
||||
class NumberSelectorConfig(BaseSelectorConfig, total=False):
|
||||
|
||||
@@ -1132,6 +1132,19 @@ async def test_selector_serializer(
|
||||
},
|
||||
"required": ["media_content_id", "media_content_type"],
|
||||
}
|
||||
assert selector_serializer(selector.MediaSelector({"multiple": True})) == {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entity_id": {"type": "string"},
|
||||
"media_content_id": {"type": "string"},
|
||||
"media_content_type": {"type": "string"},
|
||||
"metadata": {"type": "object", "additionalProperties": True},
|
||||
},
|
||||
"required": ["media_content_id", "media_content_type"],
|
||||
},
|
||||
}
|
||||
assert selector_serializer(selector.NumberSelector({"mode": "box"})) == {
|
||||
"type": "number"
|
||||
}
|
||||
|
||||
@@ -1164,6 +1164,8 @@ def test_media_selector_schema(schema, valid_selections, invalid_selections) ->
|
||||
|
||||
def drop_metadata(data):
|
||||
"""Drop metadata key from the input."""
|
||||
if isinstance(data, list):
|
||||
return [drop_metadata(item) for item in data]
|
||||
data.pop("metadata", None)
|
||||
return data
|
||||
|
||||
@@ -1176,6 +1178,96 @@ def test_media_selector_schema(schema, valid_selections, invalid_selections) ->
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("schema", "valid_selections", "invalid_selections"),
|
||||
[
|
||||
(
|
||||
{"multiple": True},
|
||||
(
|
||||
[
|
||||
{
|
||||
"entity_id": "sensor.abc",
|
||||
"media_content_id": "abc",
|
||||
"media_content_type": "def",
|
||||
},
|
||||
{
|
||||
"entity_id": "sensor.def",
|
||||
"media_content_id": "ghi",
|
||||
"media_content_type": "jkl",
|
||||
},
|
||||
],
|
||||
# Not a list is automatically converted to a list
|
||||
{
|
||||
"entity_id": "sensor.abc",
|
||||
"media_content_id": "abc",
|
||||
"media_content_type": "def",
|
||||
},
|
||||
),
|
||||
(
|
||||
None,
|
||||
# Missing required key in one item
|
||||
[
|
||||
{
|
||||
"entity_id": "sensor.abc",
|
||||
"media_content_id": "abc",
|
||||
"media_content_type": "def",
|
||||
},
|
||||
{
|
||||
"entity_id": "sensor.def",
|
||||
"media_content_id": "ghi",
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
{"multiple": True, "accept": ["image/*"]},
|
||||
(
|
||||
[
|
||||
{
|
||||
"media_content_id": "abc",
|
||||
"media_content_type": "def",
|
||||
},
|
||||
{
|
||||
"media_content_id": "ghi",
|
||||
"media_content_type": "jkl",
|
||||
},
|
||||
],
|
||||
),
|
||||
(
|
||||
None,
|
||||
# entity_id not allowed when accept is set
|
||||
[
|
||||
{
|
||||
"entity_id": "sensor.abc",
|
||||
"media_content_id": "abc",
|
||||
"media_content_type": "def",
|
||||
}
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_media_selector_schema_multiple(
|
||||
schema, valid_selections, invalid_selections
|
||||
) -> None:
|
||||
"""Test media selector with multiple selections."""
|
||||
|
||||
def drop_metadata(data, root=True):
|
||||
if isinstance(data, list):
|
||||
return [drop_metadata(item, False) for item in data]
|
||||
data.pop("metadata", None)
|
||||
# Multiple=true wraps single values in list.
|
||||
return [data] if root and schema.get("multiple") else data
|
||||
|
||||
_test_selector(
|
||||
"media",
|
||||
schema,
|
||||
valid_selections,
|
||||
invalid_selections,
|
||||
drop_metadata,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("schema", "valid_selections", "invalid_selections"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user