mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
Validate state class and unit of measurement for non-numeric sensors (#83344)
* Validate state class and unit of measurement for non-numeric sensors * Remove duration * Fix rest tests
This commit is contained in:
parent
91cdf0ecf7
commit
762eb57636
@ -718,7 +718,6 @@ class SensorEntity(Entity):
|
|||||||
unit_of_measurement = self.unit_of_measurement
|
unit_of_measurement = self.unit_of_measurement
|
||||||
value = self.native_value
|
value = self.native_value
|
||||||
device_class = self.device_class
|
device_class = self.device_class
|
||||||
state_class = self.state_class
|
|
||||||
|
|
||||||
# Received a datetime
|
# Received a datetime
|
||||||
if value is not None and device_class == DEVICE_CLASS_TIMESTAMP:
|
if value is not None and device_class == DEVICE_CLASS_TIMESTAMP:
|
||||||
@ -755,6 +754,27 @@ class SensorEntity(Entity):
|
|||||||
f"but provides state {value}:{type(value)} resulting in '{err}'"
|
f"but provides state {value}:{type(value)} resulting in '{err}'"
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
|
# Sensors with device classes indicating a non-numeric value
|
||||||
|
# should not have a state class or unit of measurement
|
||||||
|
if device_class in {
|
||||||
|
SensorDeviceClass.DATE,
|
||||||
|
SensorDeviceClass.ENUM,
|
||||||
|
SensorDeviceClass.TIMESTAMP,
|
||||||
|
}:
|
||||||
|
if self.state_class:
|
||||||
|
raise ValueError(
|
||||||
|
f"Sensor {self.entity_id} has a state class and thus indicating "
|
||||||
|
"it has a numeric value; however, it has the non-numeric "
|
||||||
|
f"device class: {device_class}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if unit_of_measurement:
|
||||||
|
raise ValueError(
|
||||||
|
f"Sensor {self.entity_id} has a unit of measurement and thus "
|
||||||
|
"indicating it has a numeric value; however, it has the "
|
||||||
|
f"non-numeric device class: {device_class}"
|
||||||
|
)
|
||||||
|
|
||||||
# Enum checks
|
# Enum checks
|
||||||
if value is not None and (
|
if value is not None and (
|
||||||
device_class == SensorDeviceClass.ENUM or self.options is not None
|
device_class == SensorDeviceClass.ENUM or self.options is not None
|
||||||
@ -767,19 +787,6 @@ class SensorEntity(Entity):
|
|||||||
f"Sensor {self.entity_id} is providing enum options, but {reason}"
|
f"Sensor {self.entity_id} is providing enum options, but {reason}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if state_class:
|
|
||||||
raise ValueError(
|
|
||||||
f"Sensor {self.entity_id} has an state_class and thus indicating "
|
|
||||||
"it has a numeric value; however, it has the enum device class"
|
|
||||||
)
|
|
||||||
|
|
||||||
if unit_of_measurement:
|
|
||||||
raise ValueError(
|
|
||||||
f"Sensor {self.entity_id} has an unit of measurement and thus "
|
|
||||||
"indicating it has a numeric value; "
|
|
||||||
"however, it has the enum device class"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (options := self.options) and value not in options:
|
if (options := self.options) and value not in options:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Sensor {self.entity_id} provides state value '{value}', "
|
f"Sensor {self.entity_id} provides state value '{value}', "
|
||||||
|
@ -243,7 +243,6 @@ async def test_setup_timestamp(
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"value_template": "{{ value_json.key }}",
|
"value_template": "{{ value_json.key }}",
|
||||||
"device_class": SensorDeviceClass.TIMESTAMP,
|
"device_class": SensorDeviceClass.TIMESTAMP,
|
||||||
"state_class": SensorStateClass.MEASUREMENT,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -255,7 +254,6 @@ async def test_setup_timestamp(
|
|||||||
state = hass.states.get("sensor.rest_sensor")
|
state = hass.states.get("sensor.rest_sensor")
|
||||||
assert state.state == "2021-11-11T11:39:00+00:00"
|
assert state.state == "2021-11-11T11:39:00+00:00"
|
||||||
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP
|
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP
|
||||||
assert state.attributes[ATTR_STATE_CLASS] is SensorStateClass.MEASUREMENT
|
|
||||||
assert "sensor.rest_sensor rendered invalid timestamp" not in caplog.text
|
assert "sensor.rest_sensor rendered invalid timestamp" not in caplog.text
|
||||||
assert "sensor.rest_sensor rendered timestamp without timezone" not in caplog.text
|
assert "sensor.rest_sensor rendered timestamp without timezone" not in caplog.text
|
||||||
|
|
||||||
|
@ -1018,18 +1018,27 @@ async def test_invalid_enumeration_entity_without_device_class(
|
|||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_invalid_enumeration_with_state_class(
|
@pytest.mark.parametrize(
|
||||||
|
"device_class",
|
||||||
|
{
|
||||||
|
SensorDeviceClass.DATE,
|
||||||
|
SensorDeviceClass.ENUM,
|
||||||
|
SensorDeviceClass.TIMESTAMP,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def test_non_numeric_device_class_with_state_class(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
enable_custom_integrations: None,
|
enable_custom_integrations: None,
|
||||||
|
device_class: SensorDeviceClass,
|
||||||
):
|
):
|
||||||
"""Test warning on numeric entities that provide an enum."""
|
"""Test error on numeric entities that provide an state class."""
|
||||||
platform = getattr(hass.components, "test.sensor")
|
platform = getattr(hass.components, "test.sensor")
|
||||||
platform.init(empty=True)
|
platform.init(empty=True)
|
||||||
platform.ENTITIES["0"] = platform.MockSensor(
|
platform.ENTITIES["0"] = platform.MockSensor(
|
||||||
name="Test",
|
name="Test",
|
||||||
native_value=42,
|
native_value=None,
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=device_class,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
options=["option1", "option2"],
|
options=["option1", "option2"],
|
||||||
)
|
)
|
||||||
@ -1038,23 +1047,32 @@ async def test_invalid_enumeration_with_state_class(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Sensor sensor.test has an state_class and thus indicating "
|
"Sensor sensor.test has a state class and thus indicating it has a numeric "
|
||||||
"it has a numeric value; however, it has the enum device class"
|
f"value; however, it has the non-numeric device class: {device_class}"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_invalid_enumeration_with_unit_of_measurement(
|
@pytest.mark.parametrize(
|
||||||
|
"device_class",
|
||||||
|
{
|
||||||
|
SensorDeviceClass.DATE,
|
||||||
|
SensorDeviceClass.ENUM,
|
||||||
|
SensorDeviceClass.TIMESTAMP,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def test_non_numeric_device_class_with_unit_of_measurement(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
enable_custom_integrations: None,
|
enable_custom_integrations: None,
|
||||||
|
device_class: SensorDeviceClass,
|
||||||
):
|
):
|
||||||
"""Test warning on numeric entities that provide an enum."""
|
"""Test error on numeric entities that provide an unit of measurement."""
|
||||||
platform = getattr(hass.components, "test.sensor")
|
platform = getattr(hass.components, "test.sensor")
|
||||||
platform.init(empty=True)
|
platform.init(empty=True)
|
||||||
platform.ENTITIES["0"] = platform.MockSensor(
|
platform.ENTITIES["0"] = platform.MockSensor(
|
||||||
name="Test",
|
name="Test",
|
||||||
native_value=42,
|
native_value=None,
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=device_class,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
options=["option1", "option2"],
|
options=["option1", "option2"],
|
||||||
)
|
)
|
||||||
@ -1063,6 +1081,6 @@ async def test_invalid_enumeration_with_unit_of_measurement(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Sensor sensor.test has an unit of measurement and thus indicating "
|
"Sensor sensor.test has a unit of measurement and thus indicating it has "
|
||||||
"it has a numeric value; however, it has the enum device class"
|
f"a numeric value; however, it has the non-numeric device class: {device_class}"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
Loading…
x
Reference in New Issue
Block a user