mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
Support shared keys starting with period in services.yaml (#118789)
This commit is contained in:
parent
ea571a6997
commit
8620bef5b0
@ -1,21 +1,16 @@
|
||||
# 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
|
||||
|
||||
turn_on:
|
||||
target:
|
||||
entity:
|
||||
domain: light
|
||||
fields:
|
||||
transition: &transition
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.TRANSITION
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 300
|
||||
unit_of_measurement: seconds
|
||||
rgb_color: &rgb_color
|
||||
filter: &color_support
|
||||
.color_support: &color_support
|
||||
attribute:
|
||||
supported_color_modes:
|
||||
- light.ColorMode.HS
|
||||
@ -23,28 +18,18 @@ turn_on:
|
||||
- light.ColorMode.RGB
|
||||
- light.ColorMode.RGBW
|
||||
- light.ColorMode.RGBWW
|
||||
example: "[255, 100, 100]"
|
||||
selector:
|
||||
color_rgb:
|
||||
rgbw_color: &rgbw_color
|
||||
filter: *color_support
|
||||
advanced: true
|
||||
example: "[255, 100, 100, 50]"
|
||||
selector:
|
||||
object:
|
||||
rgbww_color: &rgbww_color
|
||||
filter: *color_support
|
||||
advanced: true
|
||||
example: "[255, 100, 100, 50, 70]"
|
||||
selector:
|
||||
object:
|
||||
color_name: &color_name
|
||||
filter: *color_support
|
||||
advanced: true
|
||||
selector:
|
||||
select:
|
||||
translation_key: color_name
|
||||
options: &named_colors
|
||||
|
||||
.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"
|
||||
@ -194,6 +179,45 @@ turn_on:
|
||||
- "whitesmoke"
|
||||
- "yellow"
|
||||
- "yellowgreen"
|
||||
|
||||
turn_on:
|
||||
target:
|
||||
entity:
|
||||
domain: light
|
||||
fields:
|
||||
transition: &transition
|
||||
filter:
|
||||
supported_features:
|
||||
- light.LightEntityFeature.TRANSITION
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 300
|
||||
unit_of_measurement: seconds
|
||||
rgb_color: &rgb_color
|
||||
filter: *color_support
|
||||
example: "[255, 100, 100]"
|
||||
selector:
|
||||
color_rgb:
|
||||
rgbw_color: &rgbw_color
|
||||
filter: *color_support
|
||||
advanced: true
|
||||
example: "[255, 100, 100, 50]"
|
||||
selector:
|
||||
object:
|
||||
rgbww_color: &rgbww_color
|
||||
filter: *color_support
|
||||
advanced: true
|
||||
example: "[255, 100, 100, 50, 70]"
|
||||
selector:
|
||||
object:
|
||||
color_name: &color_name
|
||||
filter: *color_support
|
||||
advanced: true
|
||||
selector:
|
||||
select:
|
||||
translation_key: color_name
|
||||
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:
|
||||
|
@ -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):
|
||||
|
@ -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}
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user