|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- import typing as t
- from ast import literal_eval
- from itertools import chain
- from itertools import islice
-
- 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:
- raw = "".join([str(v) for v in chain(head, values)])
-
- try:
- return literal_eval(raw)
- 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
-
-
- 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 native_concat(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 native_concat(
- [n async for n in self.root_render_func(ctx)] # type: ignore
- )
- except Exception:
- return self.environment.handle_exception()
-
-
- NativeEnvironment.template_class = NativeTemplate
|