Support shared keys starting with period in services.yaml (#118789)

This commit is contained in:
Erik Montnemery 2024-06-11 16:31:19 +02:00 committed by GitHub
parent ea571a6997
commit 8620bef5b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 262 additions and 181 deletions

View file

@ -1,4 +1,184 @@
# Describes the format for available light services
.brightness_support: &brightness_support
attribute:
supported_color_modes:
- light.ColorMode.BRIGHTNESS
- light.ColorMode.COLOR_TEMP
- light.ColorMode.HS
- light.ColorMode.XY
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
.color_support: &color_support
attribute:
supported_color_modes:
- light.ColorMode.HS
- light.ColorMode.XY
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
.color_temp_support: &color_temp_support
attribute:
supported_color_modes:
- light.ColorMode.COLOR_TEMP
- light.ColorMode.HS
- light.ColorMode.XY
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
.named_colors: &named_colors
- "homeassistant"
- "aliceblue"
- "antiquewhite"
- "aqua"
- "aquamarine"
- "azure"
- "beige"
- "bisque"
# Black is omitted from this list as nonsensical for lights
- "blanchedalmond"
- "blue"
- "blueviolet"
- "brown"
- "burlywood"
- "cadetblue"
- "chartreuse"
- "chocolate"
- "coral"
- "cornflowerblue"
- "cornsilk"
- "crimson"
- "cyan"
- "darkblue"
- "darkcyan"
- "darkgoldenrod"
- "darkgray"
- "darkgreen"
- "darkgrey"
- "darkkhaki"
- "darkmagenta"
- "darkolivegreen"
- "darkorange"
- "darkorchid"
- "darkred"
- "darksalmon"
- "darkseagreen"
- "darkslateblue"
- "darkslategray"
- "darkslategrey"
- "darkturquoise"
- "darkviolet"
- "deeppink"
- "deepskyblue"
- "dimgray"
- "dimgrey"
- "dodgerblue"
- "firebrick"
- "floralwhite"
- "forestgreen"
- "fuchsia"
- "gainsboro"
- "ghostwhite"
- "gold"
- "goldenrod"
- "gray"
- "green"
- "greenyellow"
- "grey"
- "honeydew"
- "hotpink"
- "indianred"
- "indigo"
- "ivory"
- "khaki"
- "lavender"
- "lavenderblush"
- "lawngreen"
- "lemonchiffon"
- "lightblue"
- "lightcoral"
- "lightcyan"
- "lightgoldenrodyellow"
- "lightgray"
- "lightgreen"
- "lightgrey"
- "lightpink"
- "lightsalmon"
- "lightseagreen"
- "lightskyblue"
- "lightslategray"
- "lightslategrey"
- "lightsteelblue"
- "lightyellow"
- "lime"
- "limegreen"
- "linen"
- "magenta"
- "maroon"
- "mediumaquamarine"
- "mediumblue"
- "mediumorchid"
- "mediumpurple"
- "mediumseagreen"
- "mediumslateblue"
- "mediumspringgreen"
- "mediumturquoise"
- "mediumvioletred"
- "midnightblue"
- "mintcream"
- "mistyrose"
- "moccasin"
- "navajowhite"
- "navy"
- "navyblue"
- "oldlace"
- "olive"
- "olivedrab"
- "orange"
- "orangered"
- "orchid"
- "palegoldenrod"
- "palegreen"
- "paleturquoise"
- "palevioletred"
- "papayawhip"
- "peachpuff"
- "peru"
- "pink"
- "plum"
- "powderblue"
- "purple"
- "red"
- "rosybrown"
- "royalblue"
- "saddlebrown"
- "salmon"
- "sandybrown"
- "seagreen"
- "seashell"
- "sienna"
- "silver"
- "skyblue"
- "slateblue"
- "slategray"
- "slategrey"
- "snow"
- "springgreen"
- "steelblue"
- "tan"
- "teal"
- "thistle"
- "tomato"
- "turquoise"
- "violet"
- "wheat"
- "white"
- "whitesmoke"
- "yellow"
- "yellowgreen"
turn_on:
target:
@ -15,14 +195,7 @@ turn_on:
max: 300
unit_of_measurement: seconds
rgb_color: &rgb_color
filter: &color_support
attribute:
supported_color_modes:
- light.ColorMode.HS
- light.ColorMode.XY
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
filter: *color_support
example: "[255, 100, 100]"
selector:
color_rgb:
@ -44,156 +217,7 @@ turn_on:
selector:
select:
translation_key: color_name
options: &named_colors
- "homeassistant"
- "aliceblue"
- "antiquewhite"
- "aqua"
- "aquamarine"
- "azure"
- "beige"
- "bisque"
# Black is omitted from this list as nonsensical for lights
- "blanchedalmond"
- "blue"
- "blueviolet"
- "brown"
- "burlywood"
- "cadetblue"
- "chartreuse"
- "chocolate"
- "coral"
- "cornflowerblue"
- "cornsilk"
- "crimson"
- "cyan"
- "darkblue"
- "darkcyan"
- "darkgoldenrod"
- "darkgray"
- "darkgreen"
- "darkgrey"
- "darkkhaki"
- "darkmagenta"
- "darkolivegreen"
- "darkorange"
- "darkorchid"
- "darkred"
- "darksalmon"
- "darkseagreen"
- "darkslateblue"
- "darkslategray"
- "darkslategrey"
- "darkturquoise"
- "darkviolet"
- "deeppink"
- "deepskyblue"
- "dimgray"
- "dimgrey"
- "dodgerblue"
- "firebrick"
- "floralwhite"
- "forestgreen"
- "fuchsia"
- "gainsboro"
- "ghostwhite"
- "gold"
- "goldenrod"
- "gray"
- "green"
- "greenyellow"
- "grey"
- "honeydew"
- "hotpink"
- "indianred"
- "indigo"
- "ivory"
- "khaki"
- "lavender"
- "lavenderblush"
- "lawngreen"
- "lemonchiffon"
- "lightblue"
- "lightcoral"
- "lightcyan"
- "lightgoldenrodyellow"
- "lightgray"
- "lightgreen"
- "lightgrey"
- "lightpink"
- "lightsalmon"
- "lightseagreen"
- "lightskyblue"
- "lightslategray"
- "lightslategrey"
- "lightsteelblue"
- "lightyellow"
- "lime"
- "limegreen"
- "linen"
- "magenta"
- "maroon"
- "mediumaquamarine"
- "mediumblue"
- "mediumorchid"
- "mediumpurple"
- "mediumseagreen"
- "mediumslateblue"
- "mediumspringgreen"
- "mediumturquoise"
- "mediumvioletred"
- "midnightblue"
- "mintcream"
- "mistyrose"
- "moccasin"
- "navajowhite"
- "navy"
- "navyblue"
- "oldlace"
- "olive"
- "olivedrab"
- "orange"
- "orangered"
- "orchid"
- "palegoldenrod"
- "palegreen"
- "paleturquoise"
- "palevioletred"
- "papayawhip"
- "peachpuff"
- "peru"
- "pink"
- "plum"
- "powderblue"
- "purple"
- "red"
- "rosybrown"
- "royalblue"
- "saddlebrown"
- "salmon"
- "sandybrown"
- "seagreen"
- "seashell"
- "sienna"
- "silver"
- "skyblue"
- "slateblue"
- "slategray"
- "slategrey"
- "snow"
- "springgreen"
- "steelblue"
- "tan"
- "teal"
- "thistle"
- "tomato"
- "turquoise"
- "violet"
- "wheat"
- "white"
- "whitesmoke"
- "yellow"
- "yellowgreen"
options: *named_colors
hs_color: &hs_color
filter: *color_support
advanced: true
@ -207,15 +231,7 @@ turn_on:
selector:
object:
color_temp: &color_temp
filter: &color_temp_support
attribute:
supported_color_modes:
- light.ColorMode.COLOR_TEMP
- light.ColorMode.HS
- light.ColorMode.XY
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
filter: *color_temp_support
advanced: true
selector:
color_temp:
@ -230,16 +246,7 @@ turn_on:
min: 2000
max: 6500
brightness: &brightness
filter: &brightness_support
attribute:
supported_color_modes:
- light.ColorMode.BRIGHTNESS
- light.ColorMode.COLOR_TEMP
- light.ColorMode.HS
- light.ColorMode.XY
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
filter: *brightness_support
advanced: true
selector:
number:

View file

@ -187,7 +187,20 @@ _SERVICE_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
_SERVICES_SCHEMA = vol.Schema({cv.slug: vol.Any(None, _SERVICE_SCHEMA)})
def starts_with_dot(key: str) -> str:
"""Check if key starts with dot."""
if not key.startswith("."):
raise vol.Invalid("Key does not start with .")
return key
_SERVICES_SCHEMA = vol.Schema(
{
vol.Remove(vol.All(str, starts_with_dot)): object,
cv.slug: vol.Any(None, _SERVICE_SCHEMA),
}
)
class ServiceParams(TypedDict):

View file

@ -78,7 +78,10 @@ CUSTOM_INTEGRATION_SERVICE_SCHEMA = vol.Any(
)
CORE_INTEGRATION_SERVICES_SCHEMA = vol.Schema(
{cv.slug: CORE_INTEGRATION_SERVICE_SCHEMA}
{
vol.Remove(vol.All(str, service.starts_with_dot)): object,
cv.slug: CORE_INTEGRATION_SERVICE_SCHEMA,
}
)
CUSTOM_INTEGRATION_SERVICES_SCHEMA = vol.Schema(
{cv.slug: CUSTOM_INTEGRATION_SERVICE_SCHEMA}

View file

@ -1432,7 +1432,10 @@ def mock_config_flow(domain: str, config_flow: type[ConfigFlow]) -> None:
def mock_integration(
hass: HomeAssistant, module: MockModule, built_in: bool = True
hass: HomeAssistant,
module: MockModule,
built_in: bool = True,
top_level_files: set[str] | None = None,
) -> loader.Integration:
"""Mock an integration."""
integration = loader.Integration(
@ -1442,7 +1445,7 @@ def mock_integration(
else f"{loader.PACKAGE_CUSTOM_COMPONENTS}.{module.DOMAIN}",
pathlib.Path(""),
module.mock_manifest(),
set(),
top_level_files,
)
def mock_import_platform(platform_name: str) -> NoReturn:

View file

@ -3,6 +3,7 @@
import asyncio
from collections.abc import Iterable
from copy import deepcopy
import io
from typing import Any
from unittest.mock import AsyncMock, Mock, patch
@ -43,13 +44,16 @@ from homeassistant.helpers import (
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import async_get_integration
from homeassistant.setup import async_setup_component
from homeassistant.util.yaml.loader import parse_yaml
from tests.common import (
MockEntity,
MockModule,
MockUser,
async_mock_service,
mock_area_registry,
mock_device_registry,
mock_integration,
mock_registry,
)
@ -916,6 +920,57 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None:
assert await service.async_get_all_descriptions(hass) is descriptions
async def test_async_get_all_descriptions_dot_keys(hass: HomeAssistant) -> None:
"""Test async_get_all_descriptions with keys starting with a period."""
service_descriptions = """
.anchor: &anchor
selector:
text:
test_service:
fields:
test: *anchor
"""
domain = "test_domain"
hass.services.async_register(domain, "test_service", lambda call: None)
mock_integration(hass, MockModule(domain), top_level_files={"services.yaml"})
assert await async_setup_component(hass, domain, {})
def load_yaml(fname, secrets=None):
with io.StringIO(service_descriptions) as file:
return parse_yaml(file)
with (
patch(
"homeassistant.helpers.service._load_services_files",
side_effect=service._load_services_files,
) as proxy_load_services_files,
patch(
"homeassistant.util.yaml.loader.load_yaml",
side_effect=load_yaml,
) as mock_load_yaml,
):
descriptions = await service.async_get_all_descriptions(hass)
mock_load_yaml.assert_called_once_with("services.yaml", None)
assert proxy_load_services_files.mock_calls[0][1][1] == unordered(
[
await async_get_integration(hass, domain),
]
)
assert descriptions == {
"test_domain": {
"test_service": {
"description": "",
"fields": {"test": {"selector": {"text": None}}},
"name": "",
}
}
}
async def test_async_get_all_descriptions_failing_integration(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None: