refactor: Fix template caching, expose cached_template, Component.template API changes (#647)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Juro Oravec 2024-09-06 22:40:39 +02:00 committed by GitHub
parent 589e802625
commit 841dd77e91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 347 additions and 56 deletions

View file

@ -1,7 +1,8 @@
import functools
import sys
import typing
from pathlib import Path
from typing import Any, Callable, List, Mapping, Sequence, Tuple, Union, get_type_hints
from typing import Any, Callable, List, Mapping, Sequence, Tuple, TypeVar, Union, cast, get_type_hints
from django.utils.autoreload import autoreload_started
@ -166,3 +167,47 @@ def validate_typed_dict(value: Mapping[str, Any], dict_type: Any, prefix: str, k
# `Component 'name' got unexpected slot keys 'invalid_key'`
# `Component 'name' got unexpected data keys 'invalid_key'`
raise TypeError(f"{prefix} got unexpected {kind} keys {formatted_keys}")
TFunc = TypeVar("TFunc", bound=Callable)
def lazy_cache(
make_cache: Callable[[], Callable[[Callable], Callable]],
) -> Callable[[TFunc], TFunc]:
"""
Decorator that caches the given function similarly to `functools.lru_cache`.
But the cache is instantiated only at first invocation.
`cache` argument is a function that generates the cache function,
e.g. `functools.lru_cache()`.
"""
_cached_fn = None
def decorator(fn: TFunc) -> TFunc:
@functools.wraps(fn)
def wrapper(*args: Any, **kwargs: Any) -> Any:
# Lazily initialize the cache
nonlocal _cached_fn
if not _cached_fn:
# E.g. `lambda: functools.lru_cache(maxsize=app_settings.TEMPLATE_CACHE_SIZE)`
cache = make_cache()
_cached_fn = cache(fn)
return _cached_fn(*args, **kwargs)
# Allow to access the LRU cache methods
# See https://stackoverflow.com/a/37654201/9788634
wrapper.cache_info = lambda: _cached_fn.cache_info() # type: ignore
wrapper.cache_clear = lambda: _cached_fn.cache_clear() # type: ignore
# And allow to remove the cache instance (mostly for tests)
def cache_remove() -> None:
nonlocal _cached_fn
_cached_fn = None
wrapper.cache_remove = cache_remove # type: ignore
return cast(TFunc, wrapper)
return decorator