Fail template functions when no default specified (#71687)

This commit is contained in:
Erik Montnemery 2022-05-13 18:46:49 +02:00 committed by GitHub
parent 83080dbba8
commit 4885331509
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 213 additions and 113 deletions

View File

@ -1325,21 +1325,12 @@ def utcnow(hass: HomeAssistant) -> datetime:
return dt_util.utcnow()
def warn_no_default(function, value, default):
def raise_no_default(function, value):
"""Log warning if no default is specified."""
template, action = template_cv.get() or ("", "rendering or compiling")
_LOGGER.warning(
(
"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 2022.6"
),
function,
value,
action,
template,
function,
default,
raise ValueError(
f"Template error: {function} got invalid input '{value}' when {action} template '{template}' "
"but no default was specified"
)
@ -1361,8 +1352,7 @@ def forgiving_round(value, precision=0, method="common", default=_SENTINEL):
except (ValueError, TypeError):
# If value can't be converted to float
if default is _SENTINEL:
warn_no_default("round", value, value)
return value
raise_no_default("round", value)
return default
@ -1373,20 +1363,25 @@ def multiply(value, amount, default=_SENTINEL):
except (ValueError, TypeError):
# If value can't be converted to float
if default is _SENTINEL:
warn_no_default("multiply", value, value)
return value
raise_no_default("multiply", value)
return default
def logarithm(value, base=math.e, default=_SENTINEL):
"""Filter and function to get logarithm of the value with a specific base."""
try:
return math.log(float(value), float(base))
value_float = float(value)
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("log", value, value)
return value
raise_no_default("log", value)
return default
try:
base_float = float(base)
except (ValueError, TypeError):
if default is _SENTINEL:
raise_no_default("log", base)
return default
return math.log(value_float, base_float)
def sine(value, default=_SENTINEL):
@ -1395,8 +1390,7 @@ def sine(value, default=_SENTINEL):
return math.sin(float(value))
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("sin", value, value)
return value
raise_no_default("sin", value)
return default
@ -1406,8 +1400,7 @@ def cosine(value, default=_SENTINEL):
return math.cos(float(value))
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("cos", value, value)
return value
raise_no_default("cos", value)
return default
@ -1417,8 +1410,7 @@ def tangent(value, default=_SENTINEL):
return math.tan(float(value))
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("tan", value, value)
return value
raise_no_default("tan", value)
return default
@ -1428,8 +1420,7 @@ def arc_sine(value, default=_SENTINEL):
return math.asin(float(value))
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("asin", value, value)
return value
raise_no_default("asin", value)
return default
@ -1439,8 +1430,7 @@ def arc_cosine(value, default=_SENTINEL):
return math.acos(float(value))
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("acos", value, value)
return value
raise_no_default("acos", value)
return default
@ -1450,8 +1440,7 @@ def arc_tangent(value, default=_SENTINEL):
return math.atan(float(value))
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("atan", value, value)
return value
raise_no_default("atan", value)
return default
@ -1474,8 +1463,7 @@ def arc_tangent2(*args, default=_SENTINEL):
return math.atan2(float(args[0]), float(args[1]))
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("atan2", args, args)
return args
raise_no_default("atan2", args)
return default
@ -1485,8 +1473,7 @@ def square_root(value, default=_SENTINEL):
return math.sqrt(float(value))
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("sqrt", value, value)
return value
raise_no_default("sqrt", value)
return default
@ -1502,8 +1489,7 @@ def timestamp_custom(value, date_format=DATE_STR_FORMAT, local=True, default=_SE
except (ValueError, TypeError):
# If timestamp can't be converted
if default is _SENTINEL:
warn_no_default("timestamp_custom", value, value)
return value
raise_no_default("timestamp_custom", value)
return default
@ -1514,8 +1500,7 @@ def timestamp_local(value, default=_SENTINEL):
except (ValueError, TypeError):
# If timestamp can't be converted
if default is _SENTINEL:
warn_no_default("timestamp_local", value, value)
return value
raise_no_default("timestamp_local", value)
return default
@ -1526,8 +1511,7 @@ def timestamp_utc(value, default=_SENTINEL):
except (ValueError, TypeError):
# If timestamp can't be converted
if default is _SENTINEL:
warn_no_default("timestamp_utc", value, value)
return value
raise_no_default("timestamp_utc", value)
return default
@ -1537,8 +1521,7 @@ def forgiving_as_timestamp(value, default=_SENTINEL):
return dt_util.as_timestamp(value)
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("as_timestamp", value, None)
return None
raise_no_default("as_timestamp", value)
return default
@ -1558,8 +1541,7 @@ def strptime(string, fmt, default=_SENTINEL):
return datetime.strptime(string, fmt)
except (ValueError, AttributeError, TypeError):
if default is _SENTINEL:
warn_no_default("strptime", string, string)
return string
raise_no_default("strptime", string)
return default
@ -1618,8 +1600,7 @@ def forgiving_float(value, default=_SENTINEL):
return float(value)
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("float", value, value)
return value
raise_no_default("float", value)
return default
@ -1629,26 +1610,23 @@ def forgiving_float_filter(value, default=_SENTINEL):
return float(value)
except (ValueError, TypeError):
if default is _SENTINEL:
warn_no_default("float", value, 0)
return 0
raise_no_default("float", value)
return default
def forgiving_int(value, default=_SENTINEL, base=10):
"""Try to convert value to an int, and warn if it fails."""
"""Try to convert value to an int, and raise 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
raise_no_default("int", value)
return result
def forgiving_int_filter(value, default=_SENTINEL, base=10):
"""Try to convert value to an int, and warn if it fails."""
"""Try to convert value to an int, and raise 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
raise_no_default("int", value)
return result

View File

@ -7,8 +7,8 @@ sensor:
friendly_name: Combined Sense Energy Usage
unit_of_measurement: kW
value_template:
"{{ ((states('sensor.energy_usage') | float) + (states('sensor.energy_usage_2')
| float)) / 1000 }}"
"{{ ((states('sensor.energy_usage') | float(default=0)) + (states('sensor.energy_usage_2')
| float(default=0))) / 1000 }}"
watching_tv_in_master_bedroom:
friendly_name: Watching TV in Master Bedroom
value_template:

View File

@ -14,8 +14,8 @@ sensor:
friendly_name: Combined Sense Energy Usage
unit_of_measurement: kW
value_template:
"{{ ((states('sensor.energy_usage') | float) + (states('sensor.energy_usage_2')
| float)) / 1000 }}"
"{{ ((states('sensor.energy_usage') | float(default=0)) + (states('sensor.energy_usage_2')
| float(default=0))) / 1000 }}"
watching_tv_in_master_bedroom:
friendly_name: Watching TV in Master Bedroom
value_template:

View File

@ -1026,7 +1026,7 @@ async def test_track_template_result_none(hass):
template_condition = Template("{{state_attr('sensor.test', 'battery')}}", hass)
template_condition_var = Template(
"{{(state_attr('sensor.test', 'battery')|int) + test }}", hass
"{{(state_attr('sensor.test', 'battery')|int(default=0)) + test }}", hass
)
def specific_run_callback(event, updates):

View File

@ -233,11 +233,11 @@ def test_float_function(hass):
is True
)
assert (
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ float('forgiving') }}", hass).async_render()
== "forgiving"
)
# Test handling of default return value
assert render(hass, "{{ float('bad', 1) }}") == 1
assert render(hass, "{{ float('bad', default=1) }}") == 1
@ -248,7 +248,12 @@ def test_float_filter(hass):
assert render(hass, "{{ states.sensor.temperature.state | float }}") == 12.0
assert render(hass, "{{ states.sensor.temperature.state | float > 11 }}") is True
assert render(hass, "{{ 'bad' | float }}") == 0
# Test handling of invalid input
with pytest.raises(TemplateError):
render(hass, "{{ 'bad' | float }}")
# Test handling of default return value
assert render(hass, "{{ 'bad' | float(1) }}") == 1
assert render(hass, "{{ 'bad' | float(default=1) }}") == 1
@ -262,7 +267,11 @@ def test_int_filter(hass):
hass.states.async_set("sensor.temperature", "0x10")
assert render(hass, "{{ states.sensor.temperature.state | int(base=16) }}") == 16
assert render(hass, "{{ 'bad' | int }}") == 0
# Test handling of invalid input
with pytest.raises(TemplateError):
render(hass, "{{ 'bad' | int }}")
# Test handling of default return value
assert render(hass, "{{ 'bad' | int(1) }}") == 1
assert render(hass, "{{ 'bad' | int(default=1) }}") == 1
@ -276,7 +285,11 @@ def test_int_function(hass):
hass.states.async_set("sensor.temperature", "0x10")
assert render(hass, "{{ int(states.sensor.temperature.state, base=16) }}") == 16
assert render(hass, "{{ int('bad') }}") == "bad"
# Test handling of invalid input
with pytest.raises(TemplateError):
render(hass, "{{ int('bad') }}")
# Test handling of default return value
assert render(hass, "{{ int('bad', 1) }}") == 1
assert render(hass, "{{ int('bad', default=1) }}") == 1
@ -364,12 +377,12 @@ def test_rounding_value(hass):
def test_rounding_value_on_error(hass):
"""Test rounding value handling of error."""
assert template.Template("{{ None | round }}", hass).async_render() is None
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ None | round }}", hass).async_render()
assert (
with pytest.raises(TemplateError):
template.Template('{{ "no_number" | round }}', hass).async_render()
== "no_number"
)
# Test handling of default return value
assert render(hass, "{{ 'no_number' | round(default=1) }}") == 1
@ -377,7 +390,7 @@ def test_rounding_value_on_error(hass):
def test_multiply(hass):
"""Test multiply."""
tests = {None: None, 10: 100, '"abcd"': "abcd"}
tests = {10: 100}
for inp, out in tests.items():
assert (
@ -387,6 +400,10 @@ def test_multiply(hass):
== out
)
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ abcd | multiply(10) }}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ 'no_number' | multiply(10, 1) }}") == 1
assert render(hass, "{{ 'no_number' | multiply(10, default=1) }}") == 1
@ -397,9 +414,7 @@ def test_logarithm(hass):
tests = [
(4, 2, 2.0),
(1000, 10, 3.0),
(math.e, "", 1.0),
('"invalid"', "_", "invalid"),
(10, '"invalid"', 10.0),
(math.e, "", 1.0), # The "" means the default base (e) will be used
]
for value, base, expected in tests:
@ -417,6 +432,16 @@ def test_logarithm(hass):
== expected
)
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ invalid | log(_) }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ log(invalid, _) }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ 10 | log(invalid) }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ log(10, invalid) }}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ 'no_number' | log(10, 1) }}") == 1
assert render(hass, "{{ 'no_number' | log(10, default=1) }}") == 1
@ -432,7 +457,6 @@ def test_sine(hass):
(math.pi, 0.0),
(math.pi * 1.5, -1.0),
(math.pi / 10, 0.309),
('"duck"', "duck"),
]
for value, expected in tests:
@ -442,6 +466,12 @@ def test_sine(hass):
)
assert render(hass, f"{{{{ sin({value}) | round(3) }}}}") == expected
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ 'duck' | sin }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ invalid | sin('duck') }}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ 'no_number' | sin(1) }}") == 1
assert render(hass, "{{ 'no_number' | sin(default=1) }}") == 1
@ -457,7 +487,6 @@ def test_cos(hass):
(math.pi, -1.0),
(math.pi * 1.5, -0.0),
(math.pi / 10, 0.951),
("'error'", "error"),
]
for value, expected in tests:
@ -467,11 +496,17 @@ def test_cos(hass):
)
assert render(hass, f"{{{{ cos({value}) | round(3) }}}}") == expected
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ 'error' | cos }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ invalid | cos('error') }}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ 'no_number' | sin(1) }}") == 1
assert render(hass, "{{ 'no_number' | sin(default=1) }}") == 1
assert render(hass, "{{ sin('no_number', 1) }}") == 1
assert render(hass, "{{ sin('no_number', default=1) }}") == 1
assert render(hass, "{{ 'no_number' | cos(1) }}") == 1
assert render(hass, "{{ 'no_number' | cos(default=1) }}") == 1
assert render(hass, "{{ cos('no_number', 1) }}") == 1
assert render(hass, "{{ cos('no_number', default=1) }}") == 1
def test_tan(hass):
@ -482,7 +517,6 @@ def test_tan(hass):
(math.pi / 180 * 45, 1.0),
(math.pi / 180 * 90, "1.633123935319537e+16"),
(math.pi / 180 * 135, -1.0),
("'error'", "error"),
]
for value, expected in tests:
@ -492,6 +526,12 @@ def test_tan(hass):
)
assert render(hass, f"{{{{ tan({value}) | round(3) }}}}") == expected
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ 'error' | tan }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ invalid | tan('error') }}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ 'no_number' | tan(1) }}") == 1
assert render(hass, "{{ 'no_number' | tan(default=1) }}") == 1
@ -507,7 +547,6 @@ def test_sqrt(hass):
(2, 1.414),
(10, 3.162),
(100, 10.0),
("'error'", "error"),
]
for value, expected in tests:
@ -517,6 +556,12 @@ def test_sqrt(hass):
)
assert render(hass, f"{{{{ sqrt({value}) | round(3) }}}}") == expected
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ 'error' | sqrt }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ invalid | sqrt('error') }}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ 'no_number' | sqrt(1) }}") == 1
assert render(hass, "{{ 'no_number' | sqrt(default=1) }}") == 1
@ -527,14 +572,11 @@ def test_sqrt(hass):
def test_arc_sine(hass):
"""Test arcus sine."""
tests = [
(-2.0, -2.0), # value error
(-1.0, -1.571),
(-0.5, -0.524),
(0.0, 0.0),
(0.5, 0.524),
(1.0, 1.571),
(2.0, 2.0), # value error
('"error"', "error"),
]
for value, expected in tests:
@ -544,6 +586,19 @@ def test_arc_sine(hass):
)
assert render(hass, f"{{{{ asin({value}) | round(3) }}}}") == expected
# Test handling of invalid input
invalid_tests = [
-2.0, # value error
2.0, # value error
'"error"',
]
for value in invalid_tests:
with pytest.raises(TemplateError):
template.Template("{{ %s | asin | round(3) }}" % value, hass).async_render()
with pytest.raises(TemplateError):
assert render(hass, f"{{{{ asin({value}) | round(3) }}}}")
# Test handling of default return value
assert render(hass, "{{ 'no_number' | asin(1) }}") == 1
assert render(hass, "{{ 'no_number' | asin(default=1) }}") == 1
@ -554,14 +609,11 @@ def test_arc_sine(hass):
def test_arc_cos(hass):
"""Test arcus cosine."""
tests = [
(-2.0, -2.0), # value error
(-1.0, 3.142),
(-0.5, 2.094),
(0.0, 1.571),
(0.5, 1.047),
(1.0, 0.0),
(2.0, 2.0), # value error
('"error"', "error"),
]
for value, expected in tests:
@ -571,6 +623,19 @@ def test_arc_cos(hass):
)
assert render(hass, f"{{{{ acos({value}) | round(3) }}}}") == expected
# Test handling of invalid input
invalid_tests = [
-2.0, # value error
2.0, # value error
'"error"',
]
for value in invalid_tests:
with pytest.raises(TemplateError):
template.Template("{{ %s | acos | round(3) }}" % value, hass).async_render()
with pytest.raises(TemplateError):
assert render(hass, f"{{{{ acos({value}) | round(3) }}}}")
# Test handling of default return value
assert render(hass, "{{ 'no_number' | acos(1) }}") == 1
assert render(hass, "{{ 'no_number' | acos(default=1) }}") == 1
@ -590,7 +655,6 @@ def test_arc_tan(hass):
(1.0, 0.785),
(2.0, 1.107),
(10.0, 1.471),
('"error"', "error"),
]
for value, expected in tests:
@ -600,6 +664,12 @@ def test_arc_tan(hass):
)
assert render(hass, f"{{{{ atan({value}) | round(3) }}}}") == expected
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ 'error' | atan }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ invalid | atan('error') }}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ 'no_number' | atan(1) }}") == 1
assert render(hass, "{{ 'no_number' | atan(default=1) }}") == 1
@ -622,7 +692,6 @@ def test_arc_tan2(hass):
(-4.0, 3.0, -0.927),
(-1.0, 2.0, -0.464),
(2.0, 1.0, 1.107),
('"duck"', '"goose"', ("duck", "goose")),
]
for y, x, expected in tests:
@ -639,6 +708,12 @@ def test_arc_tan2(hass):
== expected
)
# Test handling of invalid input
with pytest.raises(TemplateError):
template.Template("{{ ('duck', 'goose') | atan2 }}", hass).async_render()
with pytest.raises(TemplateError):
template.Template("{{ atan2('duck', 'goose') }}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ ('duck', 'goose') | atan2(1) }}") == 1
assert render(hass, "{{ ('duck', 'goose') | atan2(default=1) }}") == 1
@ -655,8 +730,6 @@ def test_strptime(hass):
("2016-10-19", "%Y-%m-%d", None),
("2016", "%Y", None),
("15:22:05", "%H:%M:%S", None),
("1469119144", "%Y", 1469119144),
("invalid", "%Y", "invalid"),
]
for inp, fmt, expected in tests:
@ -667,6 +740,18 @@ def test_strptime(hass):
assert template.Template(temp, hass).async_render() == expected
# Test handling of invalid input
invalid_tests = [
("1469119144", "%Y"),
("invalid", "%Y"),
]
for inp, fmt in invalid_tests:
temp = f"{{{{ strptime('{inp}', '{fmt}') }}}}"
with pytest.raises(TemplateError):
template.Template(temp, hass).async_render()
# Test handling of default return value
assert render(hass, "{{ strptime('invalid', '%Y', 1) }}") == 1
assert render(hass, "{{ strptime('invalid', '%Y', default=1) }}") == 1
@ -677,7 +762,6 @@ def test_timestamp_custom(hass):
hass.config.set_time_zone("UTC")
now = dt_util.utcnow()
tests = [
(None, None, None, None),
(1469119144, None, True, "2016-07-21 16:39:04"),
(1469119144, "%Y", True, 2016),
(1469119144, "invalid", True, "invalid"),
@ -694,6 +778,22 @@ def test_timestamp_custom(hass):
assert template.Template(f"{{{{ {inp} | {fil} }}}}", hass).async_render() == out
# Test handling of invalid input
invalid_tests = [
(None, None, None),
]
for inp, fmt, local in invalid_tests:
if fmt:
fil = f"timestamp_custom('{fmt}')"
elif fmt and local:
fil = f"timestamp_custom('{fmt}', {local})"
else:
fil = "timestamp_custom"
with pytest.raises(TemplateError):
template.Template(f"{{{{ {inp} | {fil} }}}}", hass).async_render()
# Test handling of default return value
assert render(hass, "{{ None | timestamp_custom('invalid', True, 1) }}") == 1
assert render(hass, "{{ None | timestamp_custom(default=1) }}") == 1
@ -702,14 +802,25 @@ def test_timestamp_custom(hass):
def test_timestamp_local(hass):
"""Test the timestamps to local filter."""
hass.config.set_time_zone("UTC")
tests = {None: None, 1469119144: "2016-07-21T16:39:04+00:00"}
tests = [
(1469119144, "2016-07-21T16:39:04+00:00"),
]
for inp, out in tests.items():
for inp, out in tests:
assert (
template.Template("{{ %s | timestamp_local }}" % inp, hass).async_render()
== out
)
# Test handling of invalid input
invalid_tests = [
None,
]
for inp in invalid_tests:
with pytest.raises(TemplateError):
template.Template("{{ %s | timestamp_local }}" % inp, hass).async_render()
# Test handling of default return value
assert render(hass, "{{ None | timestamp_local(1) }}") == 1
assert render(hass, "{{ None | timestamp_local(default=1) }}") == 1
@ -1002,18 +1113,26 @@ def test_ordinal(hass):
def test_timestamp_utc(hass):
"""Test the timestamps to local filter."""
now = dt_util.utcnow()
tests = {
None: None,
1469119144: "2016-07-21T16:39:04+00:00",
dt_util.as_timestamp(now): now.isoformat(),
}
tests = [
(1469119144, "2016-07-21T16:39:04+00:00"),
(dt_util.as_timestamp(now), now.isoformat()),
]
for inp, out in tests.items():
for inp, out in tests:
assert (
template.Template("{{ %s | timestamp_utc }}" % inp, hass).async_render()
== out
)
# Test handling of invalid input
invalid_tests = [
None,
]
for inp in invalid_tests:
with pytest.raises(TemplateError):
template.Template("{{ %s | timestamp_utc }}" % inp, hass).async_render()
# Test handling of default return value
assert render(hass, "{{ None | timestamp_utc(1) }}") == 1
assert render(hass, "{{ None | timestamp_utc(default=1) }}") == 1
@ -1021,14 +1140,12 @@ def test_timestamp_utc(hass):
def test_as_timestamp(hass):
"""Test the as_timestamp function."""
assert (
template.Template('{{ as_timestamp("invalid") }}', hass).async_render() is None
)
hass.mock = None
assert (
template.Template("{{ as_timestamp(states.mock) }}", hass).async_render()
is None
)
with pytest.raises(TemplateError):
template.Template('{{ as_timestamp("invalid") }}', hass).async_render()
hass.states.async_set("test.object", None)
with pytest.raises(TemplateError):
template.Template("{{ as_timestamp(states.test.object) }}", hass).async_render()
tpl = (
'{{ as_timestamp(strptime("2024-02-03T09:10:24+0000", '
@ -1822,7 +1939,8 @@ def test_distance_function_return_none_if_invalid_state(hass):
"""Test distance function return None if invalid state."""
hass.states.async_set("test.object_2", "happy", {"latitude": 10})
tpl = template.Template("{{ distance(states.test.object_2) | round }}", hass)
assert tpl.async_render() is None
with pytest.raises(TemplateError):
tpl.async_render()
def test_distance_function_return_none_if_invalid_coord(hass):
@ -3359,7 +3477,11 @@ async def test_protected_blocked(hass):
async def test_demo_template(hass):
"""Test the demo template works as expected."""
hass.states.async_set("sun.sun", "above", {"elevation": 50, "next_rising": "later"})
hass.states.async_set(
"sun.sun",
"above",
{"elevation": 50, "next_rising": "2022-05-12T03:00:08.503651+00:00"},
)
for i in range(2):
hass.states.async_set(f"sensor.sensor{i}", "on")
@ -3375,7 +3497,7 @@ The temperature is {{ my_test_json.temperature }} {{ my_test_json.unit }}.
{% if is_state("sun.sun", "above_horizon") -%}
The sun rose {{ relative_time(states.sun.sun.last_changed) }} ago.
{%- else -%}
The sun will rise at {{ as_timestamp(strptime(state_attr("sun.sun", "next_rising"), "")) | timestamp_local }}.
The sun will rise at {{ as_timestamp(state_attr("sun.sun", "next_rising")) | timestamp_local }}.
{%- endif %}
For loop example getting 3 entity values: