1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
from __future__ import annotations
from functools import partial
from typing import TYPE_CHECKING, Any, Mapping, TypeVar
from typing_extensions import ParamSpec
from litestar.exceptions import ImproperlyConfiguredException, MissingDependencyException, TemplateNotFoundException
from litestar.template.base import (
TemplateCallableType,
TemplateEngineProtocol,
TemplateProtocol,
csrf_token,
url_for,
url_for_static_asset,
)
try:
from mako.exceptions import TemplateLookupException as MakoTemplateNotFound # type: ignore[import-untyped]
from mako.lookup import TemplateLookup # type: ignore[import-untyped]
from mako.template import Template as _MakoTemplate # type: ignore[import-untyped]
except ImportError as e:
raise MissingDependencyException("mako") from e
if TYPE_CHECKING:
from pathlib import Path
__all__ = ("MakoTemplate", "MakoTemplateEngine")
P = ParamSpec("P")
T = TypeVar("T")
class MakoTemplate(TemplateProtocol):
"""Mako template, implementing ``TemplateProtocol``"""
def __init__(self, template: _MakoTemplate, template_callables: list[tuple[str, TemplateCallableType]]) -> None:
"""Initialize a template.
Args:
template: Base ``MakoTemplate`` used by the underlying mako-engine
template_callables: List of callables passed to the template
"""
super().__init__()
self.template = template
self.template_callables = template_callables
def render(self, *args: Any, **kwargs: Any) -> str:
"""Render a template.
Args:
args: Positional arguments passed to the engines ``render`` function
kwargs: Keyword arguments passed to the engines ``render`` function
Returns:
Rendered template as a string
"""
for callable_key, template_callable in self.template_callables:
kwargs_copy = {**kwargs}
kwargs[callable_key] = partial(template_callable, kwargs_copy)
return str(self.template.render(*args, **kwargs))
class MakoTemplateEngine(TemplateEngineProtocol[MakoTemplate, Mapping[str, Any]]):
"""Mako-based TemplateEngine."""
def __init__(self, directory: Path | list[Path] | None = None, engine_instance: Any | None = None) -> None:
"""Initialize template engine.
Args:
directory: Direct path or list of directory paths from which to serve templates.
engine_instance: A mako TemplateLookup instance.
"""
super().__init__(directory, engine_instance)
if directory and engine_instance:
raise ImproperlyConfiguredException("You must provide either a directory or a mako TemplateLookup.")
if directory:
self.engine = TemplateLookup(
directories=directory if isinstance(directory, (list, tuple)) else [directory], default_filters=["h"]
)
elif engine_instance:
self.engine = engine_instance
self._template_callables: list[tuple[str, TemplateCallableType]] = []
self.register_template_callable(key="url_for_static_asset", template_callable=url_for_static_asset)
self.register_template_callable(key="csrf_token", template_callable=csrf_token)
self.register_template_callable(key="url_for", template_callable=url_for)
def get_template(self, template_name: str) -> MakoTemplate:
"""Retrieve a template by matching its name (dotted path) with files in the directory or directories provided.
Args:
template_name: A dotted path
Returns:
MakoTemplate instance
Raises:
TemplateNotFoundException: if no template is found.
"""
try:
return MakoTemplate(
template=self.engine.get_template(template_name), template_callables=self._template_callables
)
except MakoTemplateNotFound as exc:
raise TemplateNotFoundException(template_name=template_name) from exc
def register_template_callable(
self, key: str, template_callable: TemplateCallableType[Mapping[str, Any], P, T]
) -> None:
"""Register a callable on the template engine.
Args:
key: The callable key, i.e. the value to use inside the template to call the callable.
template_callable: A callable to register.
Returns:
None
"""
self._template_callables.append((key, template_callable))
def render_string(self, template_string: str, context: Mapping[str, Any]) -> str: # pyright: ignore
"""Render a template from a string with the given context.
Args:
template_string: The template string to render.
context: A dictionary of variables to pass to the template.
Returns:
The rendered template as a string.
"""
template = _MakoTemplate(template_string) # noqa: S702
return template.render(**context) # type: ignore[no-any-return]
@classmethod
def from_template_lookup(cls, template_lookup: TemplateLookup) -> MakoTemplateEngine:
"""Create a template engine from an existing mako TemplateLookup instance.
Args:
template_lookup: A mako TemplateLookup instance.
Returns:
MakoTemplateEngine instance
"""
return cls(directory=None, engine_instance=template_lookup)
|