Don't allow templating min, max, step in config entry template number (#125342)
parent
0ca0836e83
commit
cf049a07c2
|
@ -7,9 +7,14 @@ import logging
|
|||
|
||||
from homeassistant import config as conf_util
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_UNIQUE_ID, SERVICE_RELOAD
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_NAME,
|
||||
CONF_UNIQUE_ID,
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.exceptions import ConfigEntryError, HomeAssistantError
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.device import (
|
||||
async_remove_stale_devices_links_keep_current_device,
|
||||
|
@ -19,7 +24,7 @@ from homeassistant.helpers.service import async_register_admin_service
|
|||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.loader import async_get_integration
|
||||
|
||||
from .const import CONF_TRIGGER, DOMAIN, PLATFORMS
|
||||
from .const import CONF_MAX, CONF_MIN, CONF_STEP, CONF_TRIGGER, DOMAIN, PLATFORMS
|
||||
from .coordinator import TriggerUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -67,6 +72,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
entry.options.get(CONF_DEVICE_ID),
|
||||
)
|
||||
|
||||
for key in (CONF_MAX, CONF_MIN, CONF_STEP):
|
||||
if key not in entry.options:
|
||||
continue
|
||||
if isinstance(entry.options[key], str):
|
||||
raise ConfigEntryError(
|
||||
f"The '{entry.options.get(CONF_NAME) or ""}' number template needs to "
|
||||
f"be reconfigured, {key} must be a number, got '{entry.options[key]}'"
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
entry, (entry.options["template_type"],)
|
||||
)
|
||||
|
|
|
@ -107,15 +107,15 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
|
|||
if domain == Platform.NUMBER:
|
||||
schema |= {
|
||||
vol.Required(CONF_STATE): selector.TemplateSelector(),
|
||||
vol.Required(
|
||||
CONF_MIN, default=f"{{{{{DEFAULT_MIN_VALUE}}}}}"
|
||||
): selector.TemplateSelector(),
|
||||
vol.Required(
|
||||
CONF_MAX, default=f"{{{{{DEFAULT_MAX_VALUE}}}}}"
|
||||
): selector.TemplateSelector(),
|
||||
vol.Required(
|
||||
CONF_STEP, default=f"{{{{{DEFAULT_STEP}}}}}"
|
||||
): selector.TemplateSelector(),
|
||||
vol.Required(CONF_MIN, default=DEFAULT_MIN_VALUE): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX),
|
||||
),
|
||||
vol.Required(CONF_MAX, default=DEFAULT_MAX_VALUE): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX),
|
||||
),
|
||||
vol.Required(CONF_STEP, default=DEFAULT_STEP): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX),
|
||||
),
|
||||
vol.Optional(CONF_SET_VALUE): selector.ActionSelector(),
|
||||
}
|
||||
|
||||
|
|
|
@ -28,11 +28,14 @@ PLATFORMS = [
|
|||
Platform.WEATHER,
|
||||
]
|
||||
|
||||
CONF_AVAILABILITY = "availability"
|
||||
CONF_ATTRIBUTES = "attributes"
|
||||
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
|
||||
CONF_ATTRIBUTES = "attributes"
|
||||
CONF_AVAILABILITY = "availability"
|
||||
CONF_MAX = "max"
|
||||
CONF_MIN = "min"
|
||||
CONF_OBJECT_ID = "object_id"
|
||||
CONF_PICTURE = "picture"
|
||||
CONF_PRESS = "press"
|
||||
CONF_OBJECT_ID = "object_id"
|
||||
CONF_STEP = "step"
|
||||
CONF_TURN_OFF = "turn_off"
|
||||
CONF_TURN_ON = "turn_on"
|
||||
|
|
|
@ -31,7 +31,7 @@ from homeassistant.helpers.script import Script
|
|||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import TriggerUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
from .const import CONF_MAX, CONF_MIN, CONF_STEP, DOMAIN
|
||||
from .template_entity import (
|
||||
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA,
|
||||
TEMPLATE_ENTITY_ICON_SCHEMA,
|
||||
|
@ -42,9 +42,6 @@ from .trigger_entity import TriggerEntity
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_SET_VALUE = "set_value"
|
||||
CONF_MIN = "min"
|
||||
CONF_MAX = "max"
|
||||
CONF_STEP = "step"
|
||||
|
||||
DEFAULT_NAME = "Template Number"
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
|
|
|
@ -98,9 +98,9 @@ from tests.typing import WebSocketGenerator
|
|||
{"one": "30.0", "two": "20.0"},
|
||||
{},
|
||||
{
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": "0",
|
||||
"max": "100",
|
||||
"step": "0.1",
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
|
@ -108,9 +108,9 @@ from tests.typing import WebSocketGenerator
|
|||
},
|
||||
},
|
||||
{
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
|
@ -258,14 +258,14 @@ async def test_config_flow(
|
|||
"number",
|
||||
{"state": "{{ states('number.one') }}"},
|
||||
{
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": "0",
|
||||
"max": "100",
|
||||
"step": "0.1",
|
||||
},
|
||||
{
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
},
|
||||
),
|
||||
(
|
||||
|
@ -451,9 +451,9 @@ def get_suggested(schema, key):
|
|||
["30.0", "20.0"],
|
||||
{"one": "30.0", "two": "20.0"},
|
||||
{
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
|
@ -461,9 +461,9 @@ def get_suggested(schema, key):
|
|||
},
|
||||
},
|
||||
{
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
|
@ -1230,14 +1230,14 @@ async def test_option_flow_sensor_preview_config_entry_removed(
|
|||
"number",
|
||||
{"state": "{{ states('number.one') }}"},
|
||||
{
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
},
|
||||
{
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
},
|
||||
),
|
||||
(
|
||||
|
|
|
@ -319,9 +319,9 @@ async def async_yaml_patch_helper(hass: HomeAssistant, filename: str) -> None:
|
|||
"template_type": "number",
|
||||
"name": "My template",
|
||||
"state": "{{ 10 }}",
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
|
@ -330,9 +330,9 @@ async def async_yaml_patch_helper(hass: HomeAssistant, filename: str) -> None:
|
|||
},
|
||||
{
|
||||
"state": "{{ 11 }}",
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
|
@ -454,3 +454,40 @@ async def test_change_device(
|
|||
)
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
async def test_fail_non_numerical_number_settings(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test that non numerical number options causes config entry setup to fail.
|
||||
|
||||
Support for non numerical max, min and step was added in HA Core 2024.9.0 and
|
||||
removed in HA Core 2024.9.1.
|
||||
"""
|
||||
|
||||
options = {
|
||||
"template_type": "number",
|
||||
"name": "My template",
|
||||
"state": "{{ 10 }}",
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
"data": {"value": "{{ value }}"},
|
||||
},
|
||||
}
|
||||
# Setup the config entry
|
||||
template_config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options=options,
|
||||
title="Template",
|
||||
)
|
||||
template_config_entry.add_to_hass(hass)
|
||||
assert not await hass.config_entries.async_setup(template_config_entry.entry_id)
|
||||
assert (
|
||||
"The 'My template' number template needs to be reconfigured, "
|
||||
"max must be a number, got '{{ 100 }}'" in caplog.text
|
||||
)
|
||||
|
|
|
@ -58,9 +58,9 @@ async def test_setup_config_entry(
|
|||
"name": "My template",
|
||||
"template_type": "number",
|
||||
"state": "{{ 10 }}",
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
|
@ -524,9 +524,9 @@ async def test_device_id(
|
|||
"name": "My template",
|
||||
"template_type": "number",
|
||||
"state": "{{ 10 }}",
|
||||
"min": "{{ 0 }}",
|
||||
"max": "{{ 100 }}",
|
||||
"step": "{{ 0.1 }}",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 0.1,
|
||||
"set_value": {
|
||||
"action": "input_number.set_value",
|
||||
"target": {"entity_id": "input_number.test"},
|
||||
|
|
Loading…
Reference in New Issue