From b00342991272df2c0958fdf370572eef35c2ca20 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:04:28 -0700 Subject: [PATCH] Expose statistics selector, use for `recorder.get_statistics` (#147056) * Expose statistics selector, use for `recorder.get_statistics` * code review * syntax formatting * rerun ci --- .../components/recorder/services.yaml | 2 +- homeassistant/helpers/selector.py | 33 +++++++++++++++++++ tests/helpers/test_selector.py | 27 +++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/services.yaml b/homeassistant/components/recorder/services.yaml index 65aa797d91b..3ecd2be8af6 100644 --- a/homeassistant/components/recorder/services.yaml +++ b/homeassistant/components/recorder/services.yaml @@ -69,7 +69,7 @@ get_statistics: - sensor.energy_consumption - sensor.temperature selector: - entity: + statistic: multiple: true period: diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 2d7fd51cac7..322cfe34042 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -1216,6 +1216,39 @@ class SelectSelector(Selector[SelectSelectorConfig]): return [parent_schema(vol.Schema(str)(val)) for val in data] +class StatisticSelectorConfig(BaseSelectorConfig, total=False): + """Class to represent a statistic selector config.""" + + multiple: bool + + +@SELECTORS.register("statistic") +class StatisticSelector(Selector[StatisticSelectorConfig]): + """Selector of a single or list of statistics.""" + + selector_type = "statistic" + + CONFIG_SCHEMA = BASE_SELECTOR_CONFIG_SCHEMA.extend( + { + vol.Optional("multiple", default=False): cv.boolean, + } + ) + + def __init__(self, config: StatisticSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + + def __call__(self, data: Any) -> str | list[str]: + """Validate the passed selection.""" + + if not self.config["multiple"]: + stat: str = vol.Schema(str)(data) + return stat + if not isinstance(data, list): + raise vol.Invalid("Value should be a list") + return [vol.Schema(str)(val) for val in data] + + class TargetSelectorConfig(BaseSelectorConfig, total=False): """Class to represent a target selector config.""" diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 3ddbecaf48d..51ee467b029 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -1262,3 +1262,30 @@ def test_label_selector_schema(schema, valid_selections, invalid_selections) -> def test_floor_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test floor selector.""" _test_selector("floor", schema, valid_selections, invalid_selections) + + +@pytest.mark.parametrize( + ("schema", "valid_selections", "invalid_selections"), + [ + ( + {}, + ("sensor.temperature",), + (None, ["sensor.temperature"]), + ), + ( + {"multiple": True}, + (["sensor.temperature", "sensor:external_temperature"], []), + ("sensor.temperature",), + ), + ( + {"multiple": False}, + ("sensor.temperature",), + (None, ["sensor.temperature"]), + ), + ], +) +def test_statistic_selector_schema( + schema, valid_selections, invalid_selections +) -> None: + """Test statistic selector.""" + _test_selector("statistic", schema, valid_selections, invalid_selections)