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.config_entries import ConfigEntry
from homeassistant.const import ATTR_MODE, CONF_UNIT_OF_MEASUREMENT, UnitOfTemperature from homeassistant.const import ATTR_MODE, CONF_UNIT_OF_MEASUREMENT, UnitOfTemperature
from homeassistant.core import HomeAssistant, ServiceCall, async_get_hass, callback 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 ( from homeassistant.helpers.config_validation import (
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_BASE,
@ -30,6 +30,7 @@ from .const import ( # noqa: F401
ATTR_MAX, ATTR_MAX,
ATTR_MIN, ATTR_MIN,
ATTR_STEP, ATTR_STEP,
ATTR_STEP_VALIDATION,
ATTR_VALUE, ATTR_VALUE,
DEFAULT_MAX_VALUE, DEFAULT_MAX_VALUE,
DEFAULT_MIN_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.""" """Service call wrapper to set a new value."""
value = service_call.data["value"] value = service_call.data["value"]
if value < entity.min_value or value > entity.max_value: if value < entity.min_value or value > entity.max_value:
raise ValueError( raise ServiceValidationError(
f"Value {value} for {entity.entity_id} is outside valid range" translation_domain=DOMAIN,
f" {entity.min_value} - {entity.max_value}" 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: try:
native_value = entity.convert_to_native_value(value) native_value = entity.convert_to_native_value(value)
# Clamp to the native range # Clamp to the native range
@ -174,7 +182,7 @@ class NumberEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Representation of a Number entity.""" """Representation of a Number entity."""
_entity_component_unrecorded_attributes = frozenset( _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 entity_description: NumberEntityDescription

View file

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

View file

@ -161,6 +161,11 @@
"name": "[%key:component::sensor::entity_component::wind_speed::name%]" "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": { "services": {
"set_value": { "set_value": {
"name": "Set", "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.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from .test_gateway import ( from .test_gateway import (
@ -186,7 +187,7 @@ async def test_number_entities(
# Service set value beyond the supported range # Service set value beyond the supported range
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
NUMBER_DOMAIN, NUMBER_DOMAIN,
SERVICE_SET_VALUE, 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.const import ATTR_ENTITY_ID, ATTR_MODE, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
ENTITY_VOLUME = "number.volume" ENTITY_VOLUME = "number.volume"
@ -97,7 +98,7 @@ async def test_set_value_bad_range(hass: HomeAssistant) -> None:
state = hass.states.get(ENTITY_VOLUME) state = hass.states.get(ENTITY_VOLUME)
assert state.state == "42.0" assert state.state == "42.0"
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,

View file

@ -21,7 +21,7 @@ from homeassistant.const import (
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
) )
from homeassistant.core import HomeAssistant 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.helpers import entity_registry as er
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util 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" assert state.state == "4"
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
NUMBER_DOMAIN, NUMBER_DOMAIN,
SERVICE_SET_VALUE, 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.assert_called_with(pixels_per_segment=100)
bulb.async_set_device_config.reset_mock() bulb.async_set_device_config.reset_mock()
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
NUMBER_DOMAIN, NUMBER_DOMAIN,
SERVICE_SET_VALUE, 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.assert_called_with(music_pixels_per_segment=100)
bulb.async_set_device_config.reset_mock() bulb.async_set_device_config.reset_mock()
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
NUMBER_DOMAIN, NUMBER_DOMAIN,
SERVICE_SET_VALUE, 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.assert_called_with(segments=5)
bulb.async_set_device_config.reset_mock() bulb.async_set_device_config.reset_mock()
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
NUMBER_DOMAIN, NUMBER_DOMAIN,
SERVICE_SET_VALUE, 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.components.knx.schema import NumberSchema
from homeassistant.const import CONF_NAME, CONF_TYPE from homeassistant.const import CONF_NAME, CONF_TYPE
from homeassistant.core import HomeAssistant, State from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import ServiceValidationError
from .conftest import KNXTestKit 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") == "%" assert state.attributes.get("unit_of_measurement") == "%"
# set value out of range # set value out of range
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
"number", "number",
"set_value", "set_value",
{"entity_id": "number.test", "value": 101.0}, {"entity_id": "number.test", "value": 101.0},
blocking=True, blocking=True,
) )
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
"number", "number",
"set_value", "set_value",

View file

@ -36,6 +36,7 @@ from homeassistant.const import (
UnitOfVolumeFlowRate, UnitOfVolumeFlowRate,
) )
from homeassistant.core import HomeAssistant, State from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY 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") state = hass.states.get("number.test")
assert state.state == "60.0" assert state.state == "60.0"
# test ValueError trigger # test range validation
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError) as exc:
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,
{ATTR_VALUE: 110.0, ATTR_ENTITY_ID: "number.test"}, {ATTR_VALUE: 110.0, ATTR_ENTITY_ID: "number.test"},
blocking=True, 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() await hass.async_block_till_done()
state = hass.states.get("number.test") 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.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component 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
assert state.state == "2" assert state.state == "2"
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
NUMBER_DOMAIN, NUMBER_DOMAIN,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,
@ -105,7 +106,7 @@ async def test_set_number_value_out_of_range(hass: HomeAssistant) -> None:
assert state assert state
assert state.state == "2" assert state.state == "2"
with pytest.raises(ValueError): with pytest.raises(ServiceValidationError):
await hass.services.async_call( await hass.services.async_call(
NUMBER_DOMAIN, NUMBER_DOMAIN,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,