123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- import typing as t
- from ast import literal_eval
- from ast import parse
- from itertools import chain
- from itertools import islice
- from types import GeneratorType
- from . import nodes
- from .compiler import CodeGenerator
- from .compiler import Frame
- from .compiler import has_safe_repr
- from .environment import Environment
- from .environment import Template
- def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
- """Return a native Python type from the list of compiled nodes. If
- the result is a single node, its value is returned. Otherwise, the
- nodes are concatenated as strings. If the result can be parsed with
- :func:`ast.literal_eval`, the parsed value is returned. Otherwise,
- the string is returned.
- :param values: Iterable of outputs to concatenate.
- """
- head = list(islice(values, 2))
- if not head:
- return None
- if len(head) == 1:
- raw = head[0]
- if not isinstance(raw, str):
- return raw
- else:
- if isinstance(values, GeneratorType):
- values = chain(head, values)
- raw = "".join([str(v) for v in values])
- try:
- return literal_eval(
- # In Python 3.10+ ast.literal_eval removes leading spaces/tabs
- # from the given string. For backwards compatibility we need to
- # parse the string ourselves without removing leading spaces/tabs.
- parse(raw, mode="eval")
- )
- except (ValueError, SyntaxError, MemoryError):
- return raw
- class NativeCodeGenerator(CodeGenerator):
- """A code generator which renders Python types by not adding
- ``str()`` around output nodes.
- """
- @staticmethod
- def _default_finalize(value: t.Any) -> t.Any:
- return value
- def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
- return repr("".join([str(v) for v in group]))
- def _output_child_to_const(
- self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
- ) -> t.Any:
- const = node.as_const(frame.eval_ctx)
- if not has_safe_repr(const):
- raise nodes.Impossible()
- if isinstance(node, nodes.TemplateData):
- return const
- return finalize.const(const) # type: ignore
- def _output_child_pre(
- self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
- ) -> None:
- if finalize.src is not None:
- self.write(finalize.src)
- def _output_child_post(
- self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
- ) -> None:
- if finalize.src is not None:
- self.write(")")
- class NativeEnvironment(Environment):
- """An environment that renders templates to native Python types."""
- code_generator_class = NativeCodeGenerator
- concat = staticmethod(native_concat) # type: ignore
- class NativeTemplate(Template):
- environment_class = NativeEnvironment
- def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
- """Render the template to produce a native Python type. If the
- result is a single node, its value is returned. Otherwise, the
- nodes are concatenated as strings. If the result can be parsed
- with :func:`ast.literal_eval`, the parsed value is returned.
- Otherwise, the string is returned.
- """
- ctx = self.new_context(dict(*args, **kwargs))
- try:
- return self.environment_class.concat( # type: ignore
- self.root_render_func(ctx) # type: ignore
- )
- except Exception:
- return self.environment.handle_exception()
- async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
- if not self.environment.is_async:
- raise RuntimeError(
- "The environment was not created with async mode enabled."
- )
- ctx = self.new_context(dict(*args, **kwargs))
- try:
- return self.environment_class.concat( # type: ignore
- [n async for n in self.root_render_func(ctx)] # type: ignore
- )
- except Exception:
- return self.environment.handle_exception()
- NativeEnvironment.template_class = NativeTemplate
|