Don't allow templating min, max, step in config entry template number (#125342)

pull/125394/head
Erik Montnemery 2024-09-06 07:59:22 +02:00 committed by GitHub
parent 0ca0836e83
commit cf049a07c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 106 additions and 55 deletions

View File

@ -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"],)
)

View File

@ -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(),
}

View File

@ -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"

View File

@ -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

View File

@ -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,
},
),
(

View File

@ -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
)

View File

@ -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"},