From e45eab600ff053928a7bf1566984e53ceb31557f Mon Sep 17 00:00:00 2001 From: ehendrix23 Date: Tue, 28 Mar 2023 09:04:29 -0600 Subject: [PATCH] Add has_value function/test to Jinja2 template (#79550) --- homeassistant/helpers/template.py | 26 ++++++++++++++++++++- tests/helpers/test_template.py | 39 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index f21cfc08f14..d3aa7c81ffb 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -49,6 +49,7 @@ from homeassistant.const import ( ATTR_LONGITUDE, ATTR_PERSONS, ATTR_UNIT_OF_MEASUREMENT, + STATE_UNAVAILABLE, STATE_UNKNOWN, UnitOfLength, ) @@ -1470,6 +1471,15 @@ def state_attr(hass: HomeAssistant, entity_id: str, name: str) -> Any: return None +def has_value(hass: HomeAssistant, entity_id: str) -> bool: + """Test if an entity has a valid value.""" + state_obj = _get_state(hass, entity_id) + + return state_obj is not None and ( + state_obj.state not in [STATE_UNAVAILABLE, STATE_UNKNOWN] + ) + + def now(hass: HomeAssistant) -> datetime: """Record fetching now.""" if (render_info := hass.data.get(_RENDER_INFO)) is not None: @@ -2302,6 +2312,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): "is_state_attr", "state_attr", "states", + "has_value", "utcnow", "now", "device_attr", @@ -2312,11 +2323,21 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): "relative_time", "today_at", ] - hass_filters = ["closest", "expand", "device_id", "area_id", "area_name"] + hass_filters = [ + "closest", + "expand", + "device_id", + "area_id", + "area_name", + "has_value", + ] + hass_tests = ["has_value"] for glob in hass_globals: self.globals[glob] = unsupported(glob) for filt in hass_filters: self.filters[filt] = unsupported(filt) + for test in hass_tests: + self.filters[test] = unsupported(test) return self.globals["expand"] = hassfunction(expand) @@ -2332,6 +2353,9 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.filters["state_attr"] = self.globals["state_attr"] self.globals["states"] = AllStates(hass) self.filters["states"] = self.globals["states"] + self.globals["has_value"] = hassfunction(has_value) + self.filters["has_value"] = pass_context(self.globals["has_value"]) + self.tests["has_value"] = pass_eval_context(self.globals["has_value"]) self.globals["utcnow"] = hassfunction(utcnow) self.globals["now"] = hassfunction(now) self.globals["relative_time"] = hassfunction(relative_time) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index c9ef9494bf1..45237a5cbf0 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -21,6 +21,7 @@ from homeassistant.const import ( LENGTH_MILLIMETERS, MASS_GRAMS, STATE_ON, + STATE_UNAVAILABLE, TEMP_CELSIUS, VOLUME_LITERS, UnitOfPressure, @@ -1607,6 +1608,44 @@ def test_states_function(hass: HomeAssistant) -> None: assert tpl.async_render() == "available" +def test_has_value(hass): + """Test has_value method.""" + hass.states.async_set("test.value1", 1) + hass.states.async_set("test.unavailable", STATE_UNAVAILABLE) + + tpl = template.Template( + """ +{{ has_value("test.value1") }} + """, + hass, + ) + assert tpl.async_render() is True + + tpl = template.Template( + """ +{{ has_value("test.unavailable") }} + """, + hass, + ) + assert tpl.async_render() is False + + tpl = template.Template( + """ +{{ has_value("test.unknown") }} + """, + hass, + ) + assert tpl.async_render() is False + + tpl = template.Template( + """ +{% if "test.value1" is has_value %}yes{% else %}no{% endif %} + """, + hass, + ) + assert tpl.async_render() == "yes" + + @patch( "homeassistant.helpers.template.TemplateEnvironment.is_safe_callable", return_value=True,