xref: /third_party/jinja2/nativetypes.py (revision e31aef6a)
1import typing as t
2from ast import literal_eval
3from ast import parse
4from itertools import chain
5from itertools import islice
6from types import GeneratorType
7
8from . import nodes
9from .compiler import CodeGenerator
10from .compiler import Frame
11from .compiler import has_safe_repr
12from .environment import Environment
13from .environment import Template
14
15
16def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
17    """Return a native Python type from the list of compiled nodes. If
18    the result is a single node, its value is returned. Otherwise, the
19    nodes are concatenated as strings. If the result can be parsed with
20    :func:`ast.literal_eval`, the parsed value is returned. Otherwise,
21    the string is returned.
22
23    :param values: Iterable of outputs to concatenate.
24    """
25    head = list(islice(values, 2))
26
27    if not head:
28        return None
29
30    if len(head) == 1:
31        raw = head[0]
32        if not isinstance(raw, str):
33            return raw
34    else:
35        if isinstance(values, GeneratorType):
36            values = chain(head, values)
37        raw = "".join([str(v) for v in values])
38
39    try:
40        return literal_eval(
41            # In Python 3.10+ ast.literal_eval removes leading spaces/tabs
42            # from the given string. For backwards compatibility we need to
43            # parse the string ourselves without removing leading spaces/tabs.
44            parse(raw, mode="eval")
45        )
46    except (ValueError, SyntaxError, MemoryError):
47        return raw
48
49
50class NativeCodeGenerator(CodeGenerator):
51    """A code generator which renders Python types by not adding
52    ``str()`` around output nodes.
53    """
54
55    @staticmethod
56    def _default_finalize(value: t.Any) -> t.Any:
57        return value
58
59    def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
60        return repr("".join([str(v) for v in group]))
61
62    def _output_child_to_const(
63        self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
64    ) -> t.Any:
65        const = node.as_const(frame.eval_ctx)
66
67        if not has_safe_repr(const):
68            raise nodes.Impossible()
69
70        if isinstance(node, nodes.TemplateData):
71            return const
72
73        return finalize.const(const)  # type: ignore
74
75    def _output_child_pre(
76        self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
77    ) -> None:
78        if finalize.src is not None:
79            self.write(finalize.src)
80
81    def _output_child_post(
82        self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
83    ) -> None:
84        if finalize.src is not None:
85            self.write(")")
86
87
88class NativeEnvironment(Environment):
89    """An environment that renders templates to native Python types."""
90
91    code_generator_class = NativeCodeGenerator
92    concat = staticmethod(native_concat)  # type: ignore
93
94
95class NativeTemplate(Template):
96    environment_class = NativeEnvironment
97
98    def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
99        """Render the template to produce a native Python type. If the
100        result is a single node, its value is returned. Otherwise, the
101        nodes are concatenated as strings. If the result can be parsed
102        with :func:`ast.literal_eval`, the parsed value is returned.
103        Otherwise, the string is returned.
104        """
105        ctx = self.new_context(dict(*args, **kwargs))
106
107        try:
108            return self.environment_class.concat(  # type: ignore
109                self.root_render_func(ctx)
110            )
111        except Exception:
112            return self.environment.handle_exception()
113
114    async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
115        if not self.environment.is_async:
116            raise RuntimeError(
117                "The environment was not created with async mode enabled."
118            )
119
120        ctx = self.new_context(dict(*args, **kwargs))
121
122        try:
123            return self.environment_class.concat(  # type: ignore
124                [n async for n in self.root_render_func(ctx)]  # type: ignore
125            )
126        except Exception:
127            return self.environment.handle_exception()
128
129
130NativeEnvironment.template_class = NativeTemplate
131