diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index ef66154ee91..46789399050 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -16,6 +16,7 @@ import math from operator import attrgetter import random import re +import statistics import sys from typing import Any, cast from urllib.parse import urlencode as urllib_urlencode @@ -1489,6 +1490,24 @@ def fail_when_undefined(value): return value +def average(*args: Any) -> float: + """ + Filter and function to calculate the arithmetic mean of an iterable or of two or more arguments. + + The parameters may be passed as an iterable or as separate arguments. + """ + if len(args) == 0: + raise TypeError("average expected at least 1 argument, got 0") + + if len(args) == 1: + if isinstance(args[0], Iterable): + return statistics.fmean(args[0]) + + raise TypeError(f"'{type(args[0]).__name__}' object is not iterable") + + return statistics.fmean(args) + + def forgiving_float(value, default=_SENTINEL): """Try to convert value to a float.""" try: @@ -1758,6 +1777,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.filters["to_json"] = to_json self.filters["from_json"] = from_json self.filters["is_defined"] = fail_when_undefined + self.filters["average"] = average self.filters["max"] = max self.filters["min"] = min self.filters["random"] = random_every_time @@ -1796,6 +1816,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.globals["timedelta"] = timedelta self.globals["strptime"] = strptime self.globals["urlencode"] = urlencode + self.globals["average"] = average self.globals["max"] = max self.globals["min"] = min self.globals["is_number"] = is_number diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index c1f023071d0..7511960992a 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -758,6 +758,19 @@ def test_from_json(hass): assert actual_result == expected_result +def test_average(hass): + """Test the average filter.""" + assert template.Template("{{ [1, 2, 3] | average }}", hass).async_render() == 2 + assert template.Template("{{ average([1, 2, 3]) }}", hass).async_render() == 2 + assert template.Template("{{ average(1, 2, 3) }}", hass).async_render() == 2 + + with pytest.raises(TemplateError): + template.Template("{{ 1 | average }}", hass).async_render() + + with pytest.raises(TemplateError): + template.Template("{{ average() }}", hass).async_render() + + def test_min(hass): """Test the min filter.""" assert template.Template("{{ [1, 2, 3] | min }}", hass).async_render() == 1