Pass hass instance when validating templates (#89242)

* Pass hass instance when validating templates

* Update tests

* Fix validating templates without hass

* Update service tests
This commit is contained in:
Erik Montnemery 2023-03-08 17:28:53 +01:00 committed by GitHub
parent b0013247ff
commit 18cb53a35c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 14 deletions

View file

@ -85,7 +85,12 @@ from homeassistant.const import (
WEEKDAYS, WEEKDAYS,
UnitOfTemperature, UnitOfTemperature,
) )
from homeassistant.core import split_entity_id, valid_entity_id from homeassistant.core import (
HomeAssistant,
async_get_hass,
split_entity_id,
valid_entity_id,
)
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.generated import currencies from homeassistant.generated import currencies
from homeassistant.generated.countries import COUNTRIES from homeassistant.generated.countries import COUNTRIES
@ -597,7 +602,11 @@ def template(value: Any | None) -> template_helper.Template:
if isinstance(value, (list, dict, template_helper.Template)): if isinstance(value, (list, dict, template_helper.Template)):
raise vol.Invalid("template value should be a string") raise vol.Invalid("template value should be a string")
template_value = template_helper.Template(str(value)) hass: HomeAssistant | None = None
with contextlib.suppress(LookupError):
hass = async_get_hass()
template_value = template_helper.Template(str(value), hass)
try: try:
template_value.ensure_valid() template_value.ensure_valid()
@ -615,7 +624,12 @@ def dynamic_template(value: Any | None) -> template_helper.Template:
if not template_helper.is_template_string(str(value)): if not template_helper.is_template_string(str(value)):
raise vol.Invalid("template value does not contain a dynamic template") raise vol.Invalid("template value does not contain a dynamic template")
template_value = template_helper.Template(str(value)) hass: HomeAssistant | None = None
with contextlib.suppress(LookupError):
hass = async_get_hass()
template_value = template_helper.Template(str(value), hass)
try: try:
template_value.ensure_valid() template_value.ensure_valid()
return template_value return template_value

View file

@ -561,11 +561,16 @@ def test_x10_address() -> None:
schema("C11") schema("C11")
def test_template() -> None: def test_template(hass: HomeAssistant) -> None:
"""Test template validator.""" """Test template validator."""
schema = vol.Schema(cv.template) schema = vol.Schema(cv.template)
for value in (None, "{{ partial_print }", "{% if True %}Hello", ["test"]): for value in (
None,
"{{ partial_print }",
"{% if True %}Hello",
["test"],
):
with pytest.raises(vol.Invalid): with pytest.raises(vol.Invalid):
schema(value) schema(value)
@ -574,12 +579,43 @@ def test_template() -> None:
"Hello", "Hello",
"{{ beer }}", "{{ beer }}",
"{% if 1 == 1 %}Hello{% else %}World{% endif %}", "{% if 1 == 1 %}Hello{% else %}World{% endif %}",
# Function added as an extension by Home Assistant
"{{ expand('group.foo')|map(attribute='entity_id')|list }}",
# Filter added as an extension by Home Assistant
"{{ ['group.foo']|expand|map(attribute='entity_id')|list }}",
) )
for value in options: for value in options:
schema(value) schema(value)
def test_dynamic_template() -> None: async def test_template_no_hass(hass: HomeAssistant) -> None:
"""Test template validator."""
schema = vol.Schema(cv.template)
for value in (
None,
"{{ partial_print }",
"{% if True %}Hello",
["test"],
# Filter added as an extension by Home Assistant
"{{ ['group.foo']|expand|map(attribute='entity_id')|list }}",
):
with pytest.raises(vol.Invalid):
await hass.async_add_executor_job(schema, value)
options = (
1,
"Hello",
"{{ beer }}",
"{% if 1 == 1 %}Hello{% else %}World{% endif %}",
# Function added as an extension by Home Assistant
"{{ expand('group.foo')|map(attribute='entity_id')|list }}",
)
for value in options:
await hass.async_add_executor_job(schema, value)
def test_dynamic_template(hass: HomeAssistant) -> None:
"""Test dynamic template validator.""" """Test dynamic template validator."""
schema = vol.Schema(cv.dynamic_template) schema = vol.Schema(cv.dynamic_template)
@ -597,11 +633,42 @@ def test_dynamic_template() -> None:
options = ( options = (
"{{ beer }}", "{{ beer }}",
"{% if 1 == 1 %}Hello{% else %}World{% endif %}", "{% if 1 == 1 %}Hello{% else %}World{% endif %}",
# Function added as an extension by Home Assistant
"{{ expand('group.foo')|map(attribute='entity_id')|list }}",
# Filter added as an extension by Home Assistant
"{{ ['group.foo']|expand|map(attribute='entity_id')|list }}",
) )
for value in options: for value in options:
schema(value) schema(value)
async def test_dynamic_template_no_hass(hass: HomeAssistant) -> None:
"""Test dynamic template validator."""
schema = vol.Schema(cv.dynamic_template)
for value in (
None,
1,
"{{ partial_print }",
"{% if True %}Hello",
["test"],
"just a string",
# Filter added as an extension by Home Assistant
"{{ ['group.foo']|expand|map(attribute='entity_id')|list }}",
):
with pytest.raises(vol.Invalid):
await hass.async_add_executor_job(schema, value)
options = (
"{{ beer }}",
"{% if 1 == 1 %}Hello{% else %}World{% endif %}",
# Function added as an extension by Home Assistant
"{{ expand('group.foo')|map(attribute='entity_id')|list }}",
)
for value in options:
await hass.async_add_executor_job(schema, value)
def test_template_complex() -> None: def test_template_complex() -> None:
"""Test template_complex validator.""" """Test template_complex validator."""
schema = vol.Schema(cv.template_complex) schema = vol.Schema(cv.template_complex)

View file

@ -383,16 +383,18 @@ async def test_split_entity_string(hass: HomeAssistant):
async def test_not_mutate_input(hass: HomeAssistant): async def test_not_mutate_input(hass: HomeAssistant):
"""Test for immutable input.""" """Test for immutable input."""
async_mock_service(hass, "test_domain", "test_service") async_mock_service(hass, "test_domain", "test_service")
config = cv.SERVICE_SCHEMA( config = {
{ "service": "test_domain.test_service",
"service": "test_domain.test_service", "entity_id": "hello.world, sensor.beer",
"entity_id": "hello.world, sensor.beer", "data": {"hello": 1},
"data": {"hello": 1}, "data_template": {"nested": {"value": "{{ 1 + 1 }}"}},
"data_template": {"nested": {"value": "{{ 1 + 1 }}"}}, }
}
)
orig = deepcopy(config) orig = deepcopy(config)
# Validate both the original and the copy
config = cv.SERVICE_SCHEMA(config)
orig = cv.SERVICE_SCHEMA(orig)
# Only change after call is each template getting hass attached # Only change after call is each template getting hass attached
template.attach(hass, orig) template.attach(hass, orig)