mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Fail template functions when no default specified (#71687)
This commit is contained in:
parent
83080dbba8
commit
4885331509
@ -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
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user