mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Fully type min_max (#79496)
This commit is contained in:
@ -182,6 +182,7 @@ homeassistant.components.media_player.*
|
||||
homeassistant.components.media_source.*
|
||||
homeassistant.components.metoffice.*
|
||||
homeassistant.components.mikrotik.*
|
||||
homeassistant.components.min_max.*
|
||||
homeassistant.components.mjpeg.*
|
||||
homeassistant.components.modbus.*
|
||||
homeassistant.components.modem_callerid.*
|
||||
|
@ -1,8 +1,10 @@
|
||||
"""Support for displaying minimal, maximal, mean or median values."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import statistics
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -20,12 +22,17 @@ from homeassistant.const import (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.core import Event, HomeAssistant, State, callback
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.reload import async_setup_reload_service
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.typing import (
|
||||
ConfigType,
|
||||
DiscoveryInfoType,
|
||||
EventType,
|
||||
StateType,
|
||||
)
|
||||
|
||||
from . import PLATFORMS
|
||||
from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN
|
||||
@ -62,7 +69,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_ENTITY_IDS): cv.entity_ids,
|
||||
vol.Optional(CONF_ROUND_DIGITS, default=2): vol.Coerce(int),
|
||||
vol.Optional(CONF_UNIQUE_ID): str,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
@ -100,10 +107,10 @@ async def async_setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the min/max/mean sensor."""
|
||||
entity_ids = config.get(CONF_ENTITY_IDS)
|
||||
name = config.get(CONF_NAME)
|
||||
sensor_type = config.get(CONF_TYPE)
|
||||
round_digits = config.get(CONF_ROUND_DIGITS)
|
||||
entity_ids: list[str] = config[CONF_ENTITY_IDS]
|
||||
name: str | None = config.get(CONF_NAME)
|
||||
sensor_type: str = config[CONF_TYPE]
|
||||
round_digits: int = config[CONF_ROUND_DIGITS]
|
||||
unique_id = config.get(CONF_UNIQUE_ID)
|
||||
|
||||
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||
@ -113,10 +120,10 @@ async def async_setup_platform(
|
||||
)
|
||||
|
||||
|
||||
def calc_min(sensor_values):
|
||||
def calc_min(sensor_values: list[tuple[str, Any]]) -> tuple[str | None, float | None]:
|
||||
"""Calculate min value, honoring unknown states."""
|
||||
val = None
|
||||
entity_id = None
|
||||
val: float | None = None
|
||||
entity_id: str | None = None
|
||||
for sensor_id, sensor_value in sensor_values:
|
||||
if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and (
|
||||
val is None or val > sensor_value
|
||||
@ -125,10 +132,10 @@ def calc_min(sensor_values):
|
||||
return entity_id, val
|
||||
|
||||
|
||||
def calc_max(sensor_values):
|
||||
def calc_max(sensor_values: list[tuple[str, Any]]) -> tuple[str | None, float | None]:
|
||||
"""Calculate max value, honoring unknown states."""
|
||||
val = None
|
||||
entity_id = None
|
||||
val: float | None = None
|
||||
entity_id: str | None = None
|
||||
for sensor_id, sensor_value in sensor_values:
|
||||
if sensor_value not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and (
|
||||
val is None or val < sensor_value
|
||||
@ -137,7 +144,7 @@ def calc_max(sensor_values):
|
||||
return entity_id, val
|
||||
|
||||
|
||||
def calc_mean(sensor_values, round_digits):
|
||||
def calc_mean(sensor_values: list[tuple[str, Any]], round_digits: int) -> float | None:
|
||||
"""Calculate mean value, honoring unknown states."""
|
||||
result = [
|
||||
sensor_value
|
||||
@ -147,10 +154,13 @@ def calc_mean(sensor_values, round_digits):
|
||||
|
||||
if not result:
|
||||
return None
|
||||
return round(statistics.mean(result), round_digits)
|
||||
value: float = round(statistics.mean(result), round_digits)
|
||||
return value
|
||||
|
||||
|
||||
def calc_median(sensor_values, round_digits):
|
||||
def calc_median(
|
||||
sensor_values: list[tuple[str, Any]], round_digits: int
|
||||
) -> float | None:
|
||||
"""Calculate median value, honoring unknown states."""
|
||||
result = [
|
||||
sensor_value
|
||||
@ -160,10 +170,11 @@ def calc_median(sensor_values, round_digits):
|
||||
|
||||
if not result:
|
||||
return None
|
||||
return round(statistics.median(result), round_digits)
|
||||
value: float = round(statistics.median(result), round_digits)
|
||||
return value
|
||||
|
||||
|
||||
def calc_range(sensor_values, round_digits):
|
||||
def calc_range(sensor_values: list[tuple[str, Any]], round_digits: int) -> float | None:
|
||||
"""Calculate range value, honoring unknown states."""
|
||||
result = [
|
||||
sensor_value
|
||||
@ -173,7 +184,8 @@ def calc_range(sensor_values, round_digits):
|
||||
|
||||
if not result:
|
||||
return None
|
||||
return round(max(result) - min(result), round_digits)
|
||||
value: float = round(max(result) - min(result), round_digits)
|
||||
return value
|
||||
|
||||
|
||||
class MinMaxSensor(SensorEntity):
|
||||
@ -183,7 +195,14 @@ class MinMaxSensor(SensorEntity):
|
||||
_attr_should_poll = False
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
def __init__(self, entity_ids, name, sensor_type, round_digits, unique_id):
|
||||
def __init__(
|
||||
self,
|
||||
entity_ids: list[str],
|
||||
name: str | None,
|
||||
sensor_type: str,
|
||||
round_digits: int,
|
||||
unique_id: str | None,
|
||||
) -> None:
|
||||
"""Initialize the min/max sensor."""
|
||||
self._attr_unique_id = unique_id
|
||||
self._entity_ids = entity_ids
|
||||
@ -197,11 +216,17 @@ class MinMaxSensor(SensorEntity):
|
||||
self._sensor_attr = SENSOR_TYPE_TO_ATTR[self._sensor_type]
|
||||
self._unit_of_measurement = None
|
||||
self._unit_of_measurement_mismatch = False
|
||||
self.min_value = self.max_value = self.mean = self.last = self.median = None
|
||||
self.range = None
|
||||
self.min_entity_id = self.max_entity_id = self.last_entity_id = None
|
||||
self.min_value: float | None = None
|
||||
self.max_value: float | None = None
|
||||
self.mean: float | None = None
|
||||
self.last: float | None = None
|
||||
self.median: float | None = None
|
||||
self.range: float | None = None
|
||||
self.min_entity_id: str | None = None
|
||||
self.max_entity_id: str | None = None
|
||||
self.last_entity_id: str | None = None
|
||||
self.count_sensors = len(self._entity_ids)
|
||||
self.states = {}
|
||||
self.states: dict[str, Any] = {}
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle added to Hass."""
|
||||
@ -220,21 +245,22 @@ class MinMaxSensor(SensorEntity):
|
||||
self._calc_values()
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
def native_value(self) -> StateType | datetime:
|
||||
"""Return the state of the sensor."""
|
||||
if self._unit_of_measurement_mismatch:
|
||||
return None
|
||||
return getattr(self, self._sensor_attr)
|
||||
value: StateType | datetime = getattr(self, self._sensor_attr)
|
||||
return value
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self):
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit the value is expressed in."""
|
||||
if self._unit_of_measurement_mismatch:
|
||||
return "ERR"
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return the state attributes of the sensor."""
|
||||
if self._sensor_type == "min":
|
||||
return {ATTR_MIN_ENTITY_ID: self.min_entity_id}
|
||||
@ -245,10 +271,12 @@ class MinMaxSensor(SensorEntity):
|
||||
return None
|
||||
|
||||
@callback
|
||||
def _async_min_max_sensor_state_listener(self, event, update_state=True):
|
||||
def _async_min_max_sensor_state_listener(
|
||||
self, event: EventType, update_state: bool = True
|
||||
) -> None:
|
||||
"""Handle the sensor state changes."""
|
||||
new_state = event.data.get("new_state")
|
||||
entity = event.data.get("entity_id")
|
||||
new_state: State | None = event.data.get("new_state")
|
||||
entity: str = event.data["entity_id"]
|
||||
|
||||
if (
|
||||
new_state is None
|
||||
@ -296,7 +324,7 @@ class MinMaxSensor(SensorEntity):
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _calc_values(self):
|
||||
def _calc_values(self) -> None:
|
||||
"""Calculate the values."""
|
||||
sensor_values = [
|
||||
(entity_id, self.states[entity_id])
|
||||
|
10
mypy.ini
10
mypy.ini
@ -1573,6 +1573,16 @@ disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.min_max.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.mjpeg.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -12,7 +12,7 @@ from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", ("sensor",))
|
||||
async def test_config_flow(hass: HomeAssistant, platform) -> None:
|
||||
async def test_config_flow(hass: HomeAssistant, platform: str) -> None:
|
||||
"""Test the config flow."""
|
||||
input_sensors = ["sensor.input_one", "sensor.input_two"]
|
||||
|
||||
@ -66,7 +66,7 @@ def get_suggested(schema, key):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", ("sensor",))
|
||||
async def test_options(hass: HomeAssistant, platform) -> None:
|
||||
async def test_options(hass: HomeAssistant, platform: str) -> None:
|
||||
"""Test reconfiguring."""
|
||||
hass.states.async_set("sensor.input_one", "10")
|
||||
hass.states.async_set("sensor.input_two", "20")
|
||||
|
@ -35,7 +35,7 @@ RANGE_1_DIGIT = round(max(VALUES) - min(VALUES), 1)
|
||||
RANGE_4_DIGITS = round(max(VALUES) - min(VALUES), 4)
|
||||
|
||||
|
||||
async def test_default_name_sensor(hass):
|
||||
async def test_default_name_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the min sensor with a default name."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -60,7 +60,7 @@ async def test_default_name_sensor(hass):
|
||||
assert entity_ids[2] == state.attributes.get("min_entity_id")
|
||||
|
||||
|
||||
async def test_min_sensor(hass):
|
||||
async def test_min_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the min sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -92,7 +92,7 @@ async def test_min_sensor(hass):
|
||||
assert entity.unique_id == "very_unique_id"
|
||||
|
||||
|
||||
async def test_max_sensor(hass):
|
||||
async def test_max_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the max sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -119,7 +119,7 @@ async def test_max_sensor(hass):
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||
|
||||
|
||||
async def test_mean_sensor(hass):
|
||||
async def test_mean_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the mean sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -145,7 +145,7 @@ async def test_mean_sensor(hass):
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||
|
||||
|
||||
async def test_mean_1_digit_sensor(hass):
|
||||
async def test_mean_1_digit_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the mean with 1-digit precision sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -171,7 +171,7 @@ async def test_mean_1_digit_sensor(hass):
|
||||
assert str(float(MEAN_1_DIGIT)) == state.state
|
||||
|
||||
|
||||
async def test_mean_4_digit_sensor(hass):
|
||||
async def test_mean_4_digit_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the mean with 4-digit precision sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -197,7 +197,7 @@ async def test_mean_4_digit_sensor(hass):
|
||||
assert str(float(MEAN_4_DIGITS)) == state.state
|
||||
|
||||
|
||||
async def test_median_sensor(hass):
|
||||
async def test_median_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the median sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -223,7 +223,7 @@ async def test_median_sensor(hass):
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||
|
||||
|
||||
async def test_range_4_digit_sensor(hass):
|
||||
async def test_range_4_digit_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the range with 4-digit precision sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -249,7 +249,7 @@ async def test_range_4_digit_sensor(hass):
|
||||
assert str(float(RANGE_4_DIGITS)) == state.state
|
||||
|
||||
|
||||
async def test_range_1_digit_sensor(hass):
|
||||
async def test_range_1_digit_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the range with 1-digit precision sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -275,7 +275,7 @@ async def test_range_1_digit_sensor(hass):
|
||||
assert str(float(RANGE_1_DIGIT)) == state.state
|
||||
|
||||
|
||||
async def test_not_enough_sensor_value(hass):
|
||||
async def test_not_enough_sensor_value(hass: HomeAssistant) -> None:
|
||||
"""Test that there is nothing done if not enough values available."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -327,7 +327,7 @@ async def test_not_enough_sensor_value(hass):
|
||||
assert state.attributes.get("max_value") is None
|
||||
|
||||
|
||||
async def test_different_unit_of_measurement(hass):
|
||||
async def test_different_unit_of_measurement(hass: HomeAssistant) -> None:
|
||||
"""Test for different unit of measurement."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -374,7 +374,7 @@ async def test_different_unit_of_measurement(hass):
|
||||
assert state.attributes.get("unit_of_measurement") == "ERR"
|
||||
|
||||
|
||||
async def test_last_sensor(hass):
|
||||
async def test_last_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the last sensor."""
|
||||
config = {
|
||||
"sensor": {
|
||||
@ -399,7 +399,7 @@ async def test_last_sensor(hass):
|
||||
assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT
|
||||
|
||||
|
||||
async def test_reload(hass):
|
||||
async def test_reload(hass: HomeAssistant) -> None:
|
||||
"""Verify we can reload filter sensors."""
|
||||
hass.states.async_set("sensor.test_1", 12345)
|
||||
hass.states.async_set("sensor.test_2", 45678)
|
||||
|
Reference in New Issue
Block a user