[nrf52, zigbee] OnlyWith support list of components (#11533)
Co-authored-by: J. Nick Koston <nick@home-assistant.io> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from contextlib import contextmanager, suppress
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
@@ -18,6 +19,7 @@ import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
from string import ascii_letters, digits
|
||||
import typing
|
||||
import uuid as uuid_
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -1763,16 +1765,37 @@ class SplitDefault(Optional):
|
||||
|
||||
|
||||
class OnlyWith(Optional):
|
||||
"""Set the default value only if the given component is loaded."""
|
||||
"""Set the default value only if the given component(s) is/are loaded.
|
||||
|
||||
def __init__(self, key, component, default=None):
|
||||
This validator allows configuration keys to have defaults that are only applied
|
||||
when specific component(s) are loaded. Supports both single component names and
|
||||
lists of components.
|
||||
|
||||
Args:
|
||||
key: Configuration key
|
||||
component: Single component name (str) or list of component names.
|
||||
For lists, ALL components must be loaded for the default to apply.
|
||||
default: Default value to use when condition is met
|
||||
|
||||
Example:
|
||||
# Single component
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(MQTTComponent)
|
||||
|
||||
# Multiple components (all must be loaded)
|
||||
cv.OnlyWith(CONF_ZIGBEE_ID, ["zigbee", "nrf52"]): cv.use_id(Zigbee)
|
||||
"""
|
||||
|
||||
def __init__(self, key, component: str | list[str], default=None) -> None:
|
||||
super().__init__(key)
|
||||
self._component = component
|
||||
self._default = vol.default_factory(default)
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
if self._component in CORE.loaded_integrations:
|
||||
def default(self) -> Callable[[], typing.Any] | vol.Undefined:
|
||||
if isinstance(self._component, list):
|
||||
if all(c in CORE.loaded_integrations for c in self._component):
|
||||
return self._default
|
||||
elif self._component in CORE.loaded_integrations:
|
||||
return self._default
|
||||
return vol.UNDEFINED
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import string
|
||||
from hypothesis import example, given
|
||||
from hypothesis.strategies import builds, integers, ip_addresses, one_of, text
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import config_validation
|
||||
from esphome.components.esp32.const import (
|
||||
@@ -301,8 +302,6 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple)
|
||||
],
|
||||
)
|
||||
def test_require_framework_version(framework, platform, message):
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome.const import (
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
@@ -377,3 +376,129 @@ def test_require_framework_version(framework, platform, message):
|
||||
config_validation.require_framework_version(
|
||||
extra_message="test 5",
|
||||
)("test")
|
||||
|
||||
|
||||
def test_only_with_single_component_loaded() -> None:
|
||||
"""Test OnlyWith with single component when component is loaded."""
|
||||
CORE.loaded_integrations = {"mqtt"}
|
||||
|
||||
schema = config_validation.Schema(
|
||||
{
|
||||
config_validation.OnlyWith("mqtt_id", "mqtt", default="test_mqtt"): str,
|
||||
}
|
||||
)
|
||||
|
||||
result = schema({})
|
||||
assert result.get("mqtt_id") == "test_mqtt"
|
||||
|
||||
|
||||
def test_only_with_single_component_not_loaded() -> None:
|
||||
"""Test OnlyWith with single component when component is not loaded."""
|
||||
CORE.loaded_integrations = set()
|
||||
|
||||
schema = config_validation.Schema(
|
||||
{
|
||||
config_validation.OnlyWith("mqtt_id", "mqtt", default="test_mqtt"): str,
|
||||
}
|
||||
)
|
||||
|
||||
result = schema({})
|
||||
assert "mqtt_id" not in result
|
||||
|
||||
|
||||
def test_only_with_list_all_components_loaded() -> None:
|
||||
"""Test OnlyWith with list when all components are loaded."""
|
||||
CORE.loaded_integrations = {"zigbee", "nrf52"}
|
||||
|
||||
schema = config_validation.Schema(
|
||||
{
|
||||
config_validation.OnlyWith(
|
||||
"zigbee_id", ["zigbee", "nrf52"], default="test_zigbee"
|
||||
): str,
|
||||
}
|
||||
)
|
||||
|
||||
result = schema({})
|
||||
assert result.get("zigbee_id") == "test_zigbee"
|
||||
|
||||
|
||||
def test_only_with_list_partial_components_loaded() -> None:
|
||||
"""Test OnlyWith with list when only some components are loaded."""
|
||||
CORE.loaded_integrations = {"zigbee"} # Only zigbee, not nrf52
|
||||
|
||||
schema = config_validation.Schema(
|
||||
{
|
||||
config_validation.OnlyWith(
|
||||
"zigbee_id", ["zigbee", "nrf52"], default="test_zigbee"
|
||||
): str,
|
||||
}
|
||||
)
|
||||
|
||||
result = schema({})
|
||||
assert "zigbee_id" not in result
|
||||
|
||||
|
||||
def test_only_with_list_no_components_loaded() -> None:
|
||||
"""Test OnlyWith with list when no components are loaded."""
|
||||
CORE.loaded_integrations = set()
|
||||
|
||||
schema = config_validation.Schema(
|
||||
{
|
||||
config_validation.OnlyWith(
|
||||
"zigbee_id", ["zigbee", "nrf52"], default="test_zigbee"
|
||||
): str,
|
||||
}
|
||||
)
|
||||
|
||||
result = schema({})
|
||||
assert "zigbee_id" not in result
|
||||
|
||||
|
||||
def test_only_with_list_multiple_components() -> None:
|
||||
"""Test OnlyWith with list requiring three components."""
|
||||
CORE.loaded_integrations = {"comp1", "comp2", "comp3"}
|
||||
|
||||
schema = config_validation.Schema(
|
||||
{
|
||||
config_validation.OnlyWith(
|
||||
"test_id", ["comp1", "comp2", "comp3"], default="test_value"
|
||||
): str,
|
||||
}
|
||||
)
|
||||
|
||||
result = schema({})
|
||||
assert result.get("test_id") == "test_value"
|
||||
|
||||
# Test with one missing
|
||||
CORE.loaded_integrations = {"comp1", "comp2"}
|
||||
result = schema({})
|
||||
assert "test_id" not in result
|
||||
|
||||
|
||||
def test_only_with_empty_list() -> None:
|
||||
"""Test OnlyWith with empty list (edge case)."""
|
||||
CORE.loaded_integrations = set()
|
||||
|
||||
schema = config_validation.Schema(
|
||||
{
|
||||
config_validation.OnlyWith("test_id", [], default="test_value"): str,
|
||||
}
|
||||
)
|
||||
|
||||
# all([]) returns True, so default should be applied
|
||||
result = schema({})
|
||||
assert result.get("test_id") == "test_value"
|
||||
|
||||
|
||||
def test_only_with_user_value_overrides_default() -> None:
|
||||
"""Test OnlyWith respects user-provided values over defaults."""
|
||||
CORE.loaded_integrations = {"mqtt"}
|
||||
|
||||
schema = config_validation.Schema(
|
||||
{
|
||||
config_validation.OnlyWith("mqtt_id", "mqtt", default="default_id"): str,
|
||||
}
|
||||
)
|
||||
|
||||
result = schema({"mqtt_id": "custom_id"})
|
||||
assert result.get("mqtt_id") == "custom_id"
|
||||
|
||||
Reference in New Issue
Block a user