Raise ServiceValidationError on number out of range exception (#114589)
This commit is contained in:
parent
31b0b823df
commit
acf2f855fe
9 changed files with 43 additions and 18 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue