Cache transient templates compiles provided via api (#89065)

* Cache transient templates compiles provided via api

partially fixes #89047 (there is more going on here)

* add a bit more coverage just to be sure

* switch method

* Revert "switch method"

This reverts commit 0e9e1c8cbe.

* tweak

* hold hass

* empty for github flakey
This commit is contained in:
J. Nick Koston 2023-03-02 16:31:12 -10:00 committed by GitHub
parent 7365522d1f
commit 48b93e03ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 4 deletions

View file

@ -1,5 +1,6 @@
"""Rest API for Home Assistant."""
import asyncio
from functools import lru_cache
from http import HTTPStatus
import logging
@ -350,6 +351,12 @@ class APIComponentsView(HomeAssistantView):
return self.json(request.app["hass"].config.components)
@lru_cache
def _cached_template(template_str: str, hass: ha.HomeAssistant) -> template.Template:
"""Return a cached template."""
return template.Template(template_str, hass)
class APITemplateView(HomeAssistantView):
"""View to handle Template requests."""
@ -362,7 +369,7 @@ class APITemplateView(HomeAssistantView):
raise Unauthorized()
try:
data = await request.json()
tpl = template.Template(data["template"], request.app["hass"])
tpl = _cached_template(data["template"], request.app["hass"])
return tpl.async_render(variables=data.get("variables"), parse_result=False)
except (ValueError, TemplateError) as ex:
return self.json_message(

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable, Coroutine
from contextlib import suppress
from functools import wraps
from functools import lru_cache, wraps
from http import HTTPStatus
import logging
import secrets
@ -365,6 +365,12 @@ async def webhook_stream_camera(
return webhook_response(resp, registration=config_entry.data)
@lru_cache
def _cached_template(template_str: str, hass: HomeAssistant) -> template.Template:
"""Return a cached template."""
return template.Template(template_str, hass)
@WEBHOOK_COMMANDS.register("render_template")
@validate_schema(
{
@ -381,7 +387,7 @@ async def webhook_render_template(
resp = {}
for key, item in data.items():
try:
tpl = template.Template(item[ATTR_TEMPLATE], hass)
tpl = _cached_template(item[ATTR_TEMPLATE], hass)
resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES))
except TemplateError as ex:
resp[key] = {"error": str(ex)}

View file

@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from contextlib import suppress
import datetime as dt
from functools import lru_cache
import json
from typing import Any, cast
@ -424,6 +425,12 @@ def handle_ping(
connection.send_message(pong_message(msg["id"]))
@lru_cache
def _cached_template(template_str: str, hass: HomeAssistant) -> template.Template:
"""Return a cached template."""
return template.Template(template_str, hass)
@decorators.websocket_command(
{
vol.Required("type"): "render_template",
@ -440,7 +447,7 @@ async def handle_render_template(
) -> None:
"""Handle render_template command."""
template_str = msg["template"]
template_obj = template.Template(template_str, hass)
template_obj = _cached_template(template_str, hass)
variables = msg.get("variables")
timeout = msg.get("timeout")
info = None

View file

@ -349,6 +349,52 @@ async def test_api_template(hass: HomeAssistant, mock_api_client: TestClient) ->
assert body == "10"
hass.states.async_set("sensor.temperature", 20)
resp = await mock_api_client.post(
const.URL_API_TEMPLATE,
json={"template": "{{ states.sensor.temperature.state }}"},
)
body = await resp.text()
assert body == "20"
hass.states.async_remove("sensor.temperature")
resp = await mock_api_client.post(
const.URL_API_TEMPLATE,
json={"template": "{{ states.sensor.temperature.state }}"},
)
body = await resp.text()
assert body == ""
async def test_api_template_cached(
hass: HomeAssistant, mock_api_client: TestClient
) -> None:
"""Test the template API uses the cache."""
hass.states.async_set("sensor.temperature", 30)
resp = await mock_api_client.post(
const.URL_API_TEMPLATE,
json={"template": "{{ states.sensor.temperature.state }}"},
)
body = await resp.text()
assert body == "30"
hass.states.async_set("sensor.temperature", 40)
resp = await mock_api_client.post(
const.URL_API_TEMPLATE,
json={"template": "{{ states.sensor.temperature.state }}"},
)
body = await resp.text()
assert body == "40"
async def test_api_template_error(
hass: HomeAssistant, mock_api_client: TestClient