Skip to content

Commit 0613021

Browse files
authored
Use relative condition keys (#150021)
1 parent 4e2fe63 commit 0613021

File tree

9 files changed

+65
-52
lines changed

9 files changed

+65
-52
lines changed

homeassistant/components/device_automation/condition.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ async def async_get_checker(self) -> condition.ConditionCheckerType:
8080

8181

8282
CONDITIONS: dict[str, type[Condition]] = {
83-
"device": DeviceCondition,
83+
"_device": DeviceCondition,
8484
}
8585

8686

homeassistant/components/sun/condition.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def sun_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
153153

154154

155155
CONDITIONS: dict[str, type[Condition]] = {
156-
"sun": SunCondition,
156+
"_": SunCondition,
157157
}
158158

159159

homeassistant/components/zone/condition.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def if_in_zone(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
147147

148148

149149
CONDITIONS: dict[str, type[Condition]] = {
150-
"zone": ZoneCondition,
150+
"_": ZoneCondition,
151151
}
152152

153153

homeassistant/helpers/condition.py

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
from homeassistant.util.async_ import run_callback_threadsafe
5959
from homeassistant.util.hass_dict import HassKey
6060
from homeassistant.util.yaml import load_yaml_dict
61-
from homeassistant.util.yaml.loader import JSON_TYPE
6261

6362
from . import config_validation as cv, entity_registry as er
63+
from .automation import get_absolute_description_key, get_relative_description_key
6464
from .integration_platform import async_process_integration_platforms
6565
from .template import Template, render_complex
6666
from .trace import (
@@ -132,7 +132,7 @@ def starts_with_dot(key: str) -> str:
132132
_CONDITIONS_SCHEMA = vol.Schema(
133133
{
134134
vol.Remove(vol.All(str, starts_with_dot)): object,
135-
cv.slug: vol.Any(None, _CONDITION_SCHEMA),
135+
cv.underscore_slug: vol.Any(None, _CONDITION_SCHEMA),
136136
}
137137
)
138138

@@ -171,6 +171,9 @@ async def _register_condition_platform(
171171

172172
if hasattr(platform, "async_get_conditions"):
173173
for condition_key in await platform.async_get_conditions(hass):
174+
condition_key = get_absolute_description_key(
175+
integration_domain, condition_key
176+
)
174177
hass.data[CONDITIONS][condition_key] = integration_domain
175178
new_conditions.add(condition_key)
176179
else:
@@ -288,22 +291,21 @@ def wrapper(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool | N
288291

289292

290293
async def _async_get_condition_platform(
291-
hass: HomeAssistant, config: ConfigType
292-
) -> ConditionProtocol | None:
293-
condition_key: str = config[CONF_CONDITION]
294-
platform_and_sub_type = condition_key.partition(".")
294+
hass: HomeAssistant, condition_key: str
295+
) -> tuple[str, ConditionProtocol | None]:
296+
platform_and_sub_type = condition_key.split(".")
295297
platform: str | None = platform_and_sub_type[0]
296298
platform = _PLATFORM_ALIASES.get(platform, platform)
297299
if platform is None:
298-
return None
300+
return "", None
299301
try:
300302
integration = await async_get_integration(hass, platform)
301303
except IntegrationNotFound:
302304
raise HomeAssistantError(
303-
f'Invalid condition "{condition_key}" specified {config}'
305+
f'Invalid condition "{condition_key}" specified'
304306
) from None
305307
try:
306-
return await integration.async_get_platform("condition")
308+
return platform, await integration.async_get_platform("condition")
307309
except ImportError:
308310
raise HomeAssistantError(
309311
f"Integration '{platform}' does not provide condition support"
@@ -339,17 +341,20 @@ def disabled_condition(
339341

340342
return disabled_condition
341343

342-
condition: str = config[CONF_CONDITION]
344+
condition_key: str = config[CONF_CONDITION]
343345
factory: Any = None
344-
platform = await _async_get_condition_platform(hass, config)
346+
platform_domain, platform = await _async_get_condition_platform(hass, condition_key)
345347

346348
if platform is not None:
347349
condition_descriptors = await platform.async_get_conditions(hass)
348-
condition_instance = condition_descriptors[condition](hass, config)
350+
relative_condition_key = get_relative_description_key(
351+
platform_domain, condition_key
352+
)
353+
condition_instance = condition_descriptors[relative_condition_key](hass, config)
349354
return await condition_instance.async_get_checker()
350355

351356
for fmt in (ASYNC_FROM_CONFIG_FORMAT, FROM_CONFIG_FORMAT):
352-
factory = getattr(sys.modules[__name__], fmt.format(condition), None)
357+
factory = getattr(sys.modules[__name__], fmt.format(condition_key), None)
353358

354359
if factory:
355360
break
@@ -960,25 +965,33 @@ async def async_validate_condition_config(
960965
hass: HomeAssistant, config: ConfigType
961966
) -> ConfigType:
962967
"""Validate config."""
963-
condition: str = config[CONF_CONDITION]
964-
if condition in ("and", "not", "or"):
968+
condition_key: str = config[CONF_CONDITION]
969+
970+
if condition_key in ("and", "not", "or"):
965971
conditions = []
966972
for sub_cond in config["conditions"]:
967973
sub_cond = await async_validate_condition_config(hass, sub_cond)
968974
conditions.append(sub_cond)
969975
config["conditions"] = conditions
970976
return config
971977

972-
platform = await _async_get_condition_platform(hass, config)
978+
platform_domain, platform = await _async_get_condition_platform(hass, condition_key)
979+
973980
if platform is not None:
974981
condition_descriptors = await platform.async_get_conditions(hass)
975-
if not (condition_class := condition_descriptors.get(condition)):
976-
raise vol.Invalid(f"Invalid condition '{condition}' specified")
982+
relative_condition_key = get_relative_description_key(
983+
platform_domain, condition_key
984+
)
985+
if not (condition_class := condition_descriptors.get(relative_condition_key)):
986+
raise vol.Invalid(f"Invalid condition '{condition_key}' specified")
977987
return await condition_class.async_validate_config(hass, config)
978-
if platform is None and condition in ("numeric_state", "state"):
988+
989+
if platform is None and condition_key in ("numeric_state", "state"):
979990
validator = cast(
980991
Callable[[HomeAssistant, ConfigType], ConfigType],
981-
getattr(sys.modules[__name__], VALIDATE_CONFIG_FORMAT.format(condition)),
992+
getattr(
993+
sys.modules[__name__], VALIDATE_CONFIG_FORMAT.format(condition_key)
994+
),
982995
)
983996
return validator(hass, config)
984997

@@ -1088,11 +1101,11 @@ def async_extract_devices(config: ConfigType | Template) -> set[str]:
10881101
return referenced
10891102

10901103

1091-
def _load_conditions_file(hass: HomeAssistant, integration: Integration) -> JSON_TYPE:
1104+
def _load_conditions_file(integration: Integration) -> dict[str, Any]:
10921105
"""Load conditions file for an integration."""
10931106
try:
10941107
return cast(
1095-
JSON_TYPE,
1108+
dict[str, Any],
10961109
_CONDITIONS_SCHEMA(
10971110
load_yaml_dict(str(integration.file_path / "conditions.yaml"))
10981111
),
@@ -1112,11 +1125,14 @@ def _load_conditions_file(hass: HomeAssistant, integration: Integration) -> JSON
11121125

11131126

11141127
def _load_conditions_files(
1115-
hass: HomeAssistant, integrations: Iterable[Integration]
1116-
) -> dict[str, JSON_TYPE]:
1128+
integrations: Iterable[Integration],
1129+
) -> dict[str, dict[str, Any]]:
11171130
"""Load condition files for multiple integrations."""
11181131
return {
1119-
integration.domain: _load_conditions_file(hass, integration)
1132+
integration.domain: {
1133+
get_absolute_description_key(integration.domain, key): value
1134+
for key, value in _load_conditions_file(integration).items()
1135+
}
11201136
for integration in integrations
11211137
}
11221138

@@ -1137,7 +1153,7 @@ async def async_get_all_descriptions(
11371153
return descriptions_cache
11381154

11391155
# Files we loaded for missing descriptions
1140-
new_conditions_descriptions: dict[str, JSON_TYPE] = {}
1156+
new_conditions_descriptions: dict[str, dict[str, Any]] = {}
11411157
# We try to avoid making a copy in the event the cache is good,
11421158
# but now we must make a copy in case new conditions get added
11431159
# while we are loading the missing ones so we do not
@@ -1166,7 +1182,7 @@ async def async_get_all_descriptions(
11661182

11671183
if integrations:
11681184
new_conditions_descriptions = await hass.async_add_executor_job(
1169-
_load_conditions_files, hass, integrations
1185+
_load_conditions_files, integrations
11701186
)
11711187

11721188
# Make a copy of the old cache and add missing descriptions to it
@@ -1175,7 +1191,7 @@ async def async_get_all_descriptions(
11751191
domain = conditions[missing_condition]
11761192

11771193
if (
1178-
yaml_description := new_conditions_descriptions.get(domain, {}).get( # type: ignore[union-attr]
1194+
yaml_description := new_conditions_descriptions.get(domain, {}).get(
11791195
missing_condition
11801196
)
11811197
) is None:

script/hassfest/conditions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def exists(value: Any) -> Any:
4747
CONDITIONS_SCHEMA = vol.Schema(
4848
{
4949
vol.Remove(vol.All(str, condition.starts_with_dot)): object,
50-
cv.slug: CONDITION_SCHEMA,
50+
cv.underscore_slug: CONDITION_SCHEMA,
5151
}
5252
)
5353

script/hassfest/icons.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def ensure_range_is_sorted(value: dict) -> dict:
126126
vol.Optional("condition"): icon_value_validator,
127127
}
128128
),
129-
slug_validator=translation_key_validator,
129+
slug_validator=cv.underscore_slug,
130130
)
131131

132132

script/hassfest/translations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
434434
slug_validator=translation_key_validator,
435435
),
436436
},
437-
slug_validator=translation_key_validator,
437+
slug_validator=cv.underscore_slug,
438438
),
439439
vol.Optional("triggers"): cv.schema_with_slug_keys(
440440
{

tests/components/websocket_api/test_commands.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -721,10 +721,10 @@ async def test_subscribe_conditions(
721721
) -> None:
722722
"""Test condition_platforms/subscribe command."""
723723
sun_condition_descriptions = """
724-
sun: {}
724+
_: {}
725725
"""
726726
device_automation_condition_descriptions = """
727-
device: {}
727+
_device: {}
728728
"""
729729

730730
def _load_yaml(fname, secrets=None):
@@ -2738,10 +2738,7 @@ async def test_validate_config_works(
27382738
"entity_id": "hello.world",
27392739
"state": "paulus",
27402740
},
2741-
(
2742-
"Invalid condition \"non_existing\" specified {'condition': "
2743-
"'non_existing', 'entity_id': 'hello.world', 'state': 'paulus'}"
2744-
),
2741+
'Invalid condition "non_existing" specified',
27452742
),
27462743
# Raises HomeAssistantError
27472744
(

tests/helpers/test_condition.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2073,7 +2073,7 @@ async def test_platform_async_get_conditions(hass: HomeAssistant) -> None:
20732073
config = {CONF_DEVICE_ID: "test", CONF_DOMAIN: "test", CONF_CONDITION: "device"}
20742074
with patch(
20752075
"homeassistant.components.device_automation.condition.async_get_conditions",
2076-
AsyncMock(return_value={"device": AsyncMock()}),
2076+
AsyncMock(return_value={"_device": AsyncMock()}),
20772077
) as device_automation_async_get_conditions_mock:
20782078
await condition.async_validate_condition_config(hass, config)
20792079
device_automation_async_get_conditions_mock.assert_awaited()
@@ -2113,8 +2113,8 @@ async def async_get_conditions(
21132113
hass: HomeAssistant,
21142114
) -> dict[str, type[condition.Condition]]:
21152115
return {
2116-
"test": MockCondition1,
2117-
"test.cond_2": MockCondition2,
2116+
"_": MockCondition1,
2117+
"cond_2": MockCondition2,
21182118
}
21192119

21202120
mock_integration(hass, MockModule("test"))
@@ -2337,7 +2337,7 @@ async def test_or_condition_with_disabled_condition(hass: HomeAssistant) -> None
23372337
"sun_condition_descriptions",
23382338
[
23392339
"""
2340-
sun:
2340+
_:
23412341
fields:
23422342
after:
23432343
example: sunrise
@@ -2371,7 +2371,7 @@ async def test_or_condition_with_disabled_condition(hass: HomeAssistant) -> None
23712371
.offset_selector: &offset_selector
23722372
selector:
23732373
time: null
2374-
sun:
2374+
_:
23752375
fields:
23762376
after: *sunrise_sunset_selector
23772377
after_offset: *offset_selector
@@ -2385,7 +2385,7 @@ async def test_async_get_all_descriptions(
23852385
) -> None:
23862386
"""Test async_get_all_descriptions."""
23872387
device_automation_condition_descriptions = """
2388-
device: {}
2388+
_device: {}
23892389
"""
23902390

23912391
assert await async_setup_component(hass, DOMAIN_SUN, {})
@@ -2415,15 +2415,15 @@ def _load_yaml(fname, secrets=None):
24152415

24162416
# Test we only load conditions.yaml for integrations with conditions,
24172417
# system_health has no conditions
2418-
assert proxy_load_conditions_files.mock_calls[0][1][1] == unordered(
2418+
assert proxy_load_conditions_files.mock_calls[0][1][0] == unordered(
24192419
[
24202420
await async_get_integration(hass, DOMAIN_SUN),
24212421
]
24222422
)
24232423

24242424
# system_health does not have conditions and should not be in descriptions
24252425
assert descriptions == {
2426-
DOMAIN_SUN: {
2426+
"sun": {
24272427
"fields": {
24282428
"after": {
24292429
"example": "sunrise",
@@ -2459,7 +2459,7 @@ def _load_yaml(fname, secrets=None):
24592459
"device": {
24602460
"fields": {},
24612461
},
2462-
DOMAIN_SUN: {
2462+
"sun": {
24632463
"fields": {
24642464
"after": {
24652465
"example": "sunrise",
@@ -2525,7 +2525,7 @@ async def test_async_get_all_descriptions_with_bad_description(
25252525
) -> None:
25262526
"""Test async_get_all_descriptions."""
25272527
sun_service_descriptions = """
2528-
sun:
2528+
_:
25292529
fields: not_a_dict
25302530
"""
25312531

@@ -2545,11 +2545,11 @@ def _load_yaml(fname, secrets=None):
25452545
):
25462546
descriptions = await condition.async_get_all_descriptions(hass)
25472547

2548-
assert descriptions == {DOMAIN_SUN: None}
2548+
assert descriptions == {"sun": None}
25492549

25502550
assert (
25512551
"Unable to parse conditions.yaml for the sun integration: "
2552-
"expected a dictionary for dictionary value @ data['sun']['fields']"
2552+
"expected a dictionary for dictionary value @ data['_']['fields']"
25532553
) in caplog.text
25542554

25552555

0 commit comments

Comments
 (0)