Reduce template render overhead (#103343)

The contextmanager decorator creates a new context manager every
time its run, but since we only have a single context var, we can
use the same one every time. Creating the contextmanager was roughly
20% of the time time of the template render

I was a bit suprised to find it creates a new context manager
object every time https://stackoverflow.com/questions/34872535/why-contextmanager-is-slow
This commit is contained in:
J. Nick Koston 2023-11-04 03:46:01 -05:00 committed by GitHub
parent 62067fc64c
commit 68471b6da5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -6,7 +6,7 @@ import asyncio
import base64
import collections.abc
from collections.abc import Callable, Collection, Generator, Iterable, MutableMapping
from contextlib import contextmanager, suppress
from contextlib import AbstractContextManager, suppress
from contextvars import ContextVar
from datetime import datetime, timedelta
from functools import cache, lru_cache, partial, wraps
@ -20,7 +20,7 @@ import re
import statistics
from struct import error as StructError, pack, unpack_from
import sys
from types import CodeType
from types import CodeType, TracebackType
from typing import (
Any,
Concatenate,
@ -504,7 +504,8 @@ class Template:
def ensure_valid(self) -> None:
"""Return if template is valid."""
with set_template(self.template, "compiling"):
with _template_context_manager as cm:
cm.set_template(self.template, "compiling")
if self.is_static or self._compiled_code is not None:
return
@ -2213,21 +2214,32 @@ def iif(
return if_false
@contextmanager
def set_template(template_str: str, action: str) -> Generator:
"""Store template being parsed or rendered in a Contextvar to aid error handling."""
template_cv.set((template_str, action))
try:
yield
finally:
class TemplateContextManager(AbstractContextManager):
"""Context manager to store template being parsed or rendered in a ContextVar."""
def set_template(self, template_str: str, action: str) -> None:
"""Store template being parsed or rendered in a Contextvar to aid error handling."""
template_cv.set((template_str, action))
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
"""Raise any exception triggered within the runtime context."""
template_cv.set(None)
_template_context_manager = TemplateContextManager()
def _render_with_context(
template_str: str, template: jinja2.Template, **kwargs: Any
) -> str:
"""Store template being rendered in a ContextVar to aid error handling."""
with set_template(template_str, "rendering"):
with _template_context_manager as cm:
cm.set_template(template_str, "rendering")
return template.render(**kwargs)