diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 019b3aaf5fb..b12ebca53c9 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1211,7 +1211,7 @@ def warn_no_default(function, value, default): ( "Template warning: '%s' got invalid input '%s' when %s template '%s' " "but no default was specified. Currently '%s' will return '%s', however this template will fail " - "to render in Home Assistant core 2021.12" + "to render in Home Assistant core 2022.1" ), function, value, @@ -1463,6 +1463,24 @@ def forgiving_float_filter(value, default=_SENTINEL): return default +def forgiving_int(value, default=_SENTINEL, base=10): + """Try to convert value to an int, and warn if it fails.""" + result = jinja2.filters.do_int(value, default=default, base=base) + if result is _SENTINEL: + warn_no_default("int", value, value) + return value + return result + + +def forgiving_int_filter(value, default=_SENTINEL, base=10): + """Try to convert value to an int, and warn if it fails.""" + result = jinja2.filters.do_int(value, default=default, base=base) + if result is _SENTINEL: + warn_no_default("int", value, 0) + return 0 + return result + + def is_number(value): """Try to convert value to a float.""" try: @@ -1693,6 +1711,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.filters["ord"] = ord self.filters["is_number"] = is_number self.filters["float"] = forgiving_float_filter + self.filters["int"] = forgiving_int_filter self.globals["log"] = logarithm self.globals["sin"] = sine self.globals["cos"] = cosine @@ -1716,6 +1735,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.globals["max"] = max self.globals["min"] = min self.globals["is_number"] = is_number + self.globals["int"] = forgiving_int self.tests["match"] = regex_match self.tests["search"] = regex_search diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 5522956c81c..4be9d527d31 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -240,6 +240,34 @@ def test_float_filter(hass): assert render(hass, "{{ 'bad' | float(default=1) }}") == 1 +def test_int_filter(hass): + """Test int filter.""" + hass.states.async_set("sensor.temperature", "12.2") + assert render(hass, "{{ states.sensor.temperature.state | int }}") == 12 + assert render(hass, "{{ states.sensor.temperature.state | int > 11 }}") is True + + hass.states.async_set("sensor.temperature", "0x10") + assert render(hass, "{{ states.sensor.temperature.state | int(base=16) }}") == 16 + + assert render(hass, "{{ 'bad' | int }}") == 0 + assert render(hass, "{{ 'bad' | int(1) }}") == 1 + assert render(hass, "{{ 'bad' | int(default=1) }}") == 1 + + +def test_int_function(hass): + """Test int filter.""" + hass.states.async_set("sensor.temperature", "12.2") + assert render(hass, "{{ int(states.sensor.temperature.state) }}") == 12 + assert render(hass, "{{ int(states.sensor.temperature.state) > 11 }}") is True + + hass.states.async_set("sensor.temperature", "0x10") + assert render(hass, "{{ int(states.sensor.temperature.state, base=16) }}") == 16 + + assert render(hass, "{{ int('bad') }}") == "bad" + assert render(hass, "{{ int('bad', 1) }}") == 1 + assert render(hass, "{{ int('bad', default=1) }}") == 1 + + @pytest.mark.parametrize( "value, expected", [