Raise ServiceValidationError on number out of range exception (#114589)

This commit is contained in:
Jan Bouwhuis 2024-04-02 12:22:00 +02:00 committed by GitHub
parent 31b0b823df
commit acf2f855fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 43 additions and 18 deletions

View file

@ -15,7 +15,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_MODE, CONF_UNIT_OF_MEASUREMENT, UnitOfTemperature
from homeassistant.core import HomeAssistant, ServiceCall, async_get_hass, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.config_validation import (
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
@ -30,6 +30,7 @@ from .const import ( # noqa: F401
ATTR_MAX,
ATTR_MIN,
ATTR_STEP,
ATTR_STEP_VALIDATION,
ATTR_VALUE,
DEFAULT_MAX_VALUE,
DEFAULT_MIN_VALUE,
@ -99,10 +100,17 @@ async def async_set_value(entity: NumberEntity, service_call: ServiceCall) -> No
"""Service call wrapper to set a new value."""
value = service_call.data["value"]
if value < entity.min_value or value > entity.max_value:
raise ValueError(
f"Value {value} for {entity.entity_id} is outside valid range"
f" {entity.min_value} - {entity.max_value}"
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="out_of_range",
translation_placeholders={
"value": value,
"entity_id": entity.entity_id,
"min_value": str(entity.min_value),
"max_value": str(entity.max_value),
},
)
try:
native_value = entity.convert_to_native_value(value)
# Clamp to the native range
@ -174,7 +182,7 @@ class NumberEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Representation of a Number entity."""
_entity_component_unrecorded_attributes = frozenset(
{ATTR_MIN, ATTR_MAX, ATTR_STEP, ATTR_MODE}
{ATTR_MIN, ATTR_MAX, ATTR_STEP, ATTR_STEP_VALIDATION, ATTR_MODE}
)
entity_description: NumberEntityDescription

View file

@ -54,6 +54,7 @@ ATTR_VALUE = "value"
ATTR_MIN = "min"
ATTR_MAX = "max"
ATTR_STEP = "step"
ATTR_STEP_VALIDATION = "step_validation"
DEFAULT_MIN_VALUE = 0.0
DEFAULT_MAX_VALUE = 100.0

View file

@ -161,6 +161,11 @@
"name": "[%key:component::sensor::entity_component::wind_speed::name%]"
}
},
"exceptions": {
"out_of_range": {
"message": "Value {value} for {entity_id} is outside valid range {min_value} - {max_value}."
}
},
"services": {
"set_value": {
"name": "Set",

View file

@ -11,6 +11,7 @@ from homeassistant.components.number import (
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import device_registry as dr, entity_registry as er
from .test_gateway import (
@ -186,7 +187,7 @@ async def test_number_entities(
# Service set value beyond the supported range
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,

View file

@ -16,6 +16,7 @@ from homeassistant.components.number import (
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.setup import async_setup_component
ENTITY_VOLUME = "number.volume"
@ -97,7 +98,7 @@ async def test_set_value_bad_range(hass: HomeAssistant) -> None:
state = hass.states.get(ENTITY_VOLUME)
assert state.state == "42.0"
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,

View file

@ -21,7 +21,7 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
@ -292,7 +292,7 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None:
assert state.state == "4"
await hass.async_block_till_done(wait_background_tasks=True)
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
@ -314,7 +314,7 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None:
bulb.async_set_device_config.assert_called_with(pixels_per_segment=100)
bulb.async_set_device_config.reset_mock()
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
@ -335,7 +335,7 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None:
bulb.async_set_device_config.assert_called_with(music_pixels_per_segment=100)
bulb.async_set_device_config.reset_mock()
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
@ -356,7 +356,7 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None:
bulb.async_set_device_config.assert_called_with(segments=5)
bulb.async_set_device_config.reset_mock()
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,

View file

@ -6,6 +6,7 @@ from homeassistant.components.knx.const import CONF_RESPOND_TO_READ, KNX_ADDRESS
from homeassistant.components.knx.schema import NumberSchema
from homeassistant.const import CONF_NAME, CONF_TYPE
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import ServiceValidationError
from .conftest import KNXTestKit
@ -37,14 +38,14 @@ async def test_number_set_value(hass: HomeAssistant, knx: KNXTestKit) -> None:
assert state.attributes.get("unit_of_measurement") == "%"
# set value out of range
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
"number",
"set_value",
{"entity_id": "number.test", "value": 101.0},
blocking=True,
)
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
"number",
"set_value",

View file

@ -36,6 +36,7 @@ from homeassistant.const import (
UnitOfVolumeFlowRate,
)
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY
@ -359,14 +360,20 @@ async def test_set_value(
state = hass.states.get("number.test")
assert state.state == "60.0"
# test ValueError trigger
with pytest.raises(ValueError):
# test range validation
with pytest.raises(ServiceValidationError) as exc:
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_VALUE: 110.0, ATTR_ENTITY_ID: "number.test"},
blocking=True,
)
assert exc.value.translation_domain == DOMAIN
assert exc.value.translation_key == "out_of_range"
assert (
str(exc.value)
== "Value 110.0 for number.test is outside valid range 0.0 - 100.0"
)
await hass.async_block_till_done()
state = hass.states.get("number.test")

View file

@ -14,6 +14,7 @@ from homeassistant.components.number import (
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
@ -86,7 +87,7 @@ async def test_set_number_value_out_of_range(hass: HomeAssistant) -> None:
assert state
assert state.state == "2"
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
@ -105,7 +106,7 @@ async def test_set_number_value_out_of_range(hass: HomeAssistant) -> None:
assert state
assert state.state == "2"
with pytest.raises(ValueError):
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,