Skip to content

Commit

Permalink
native only evals at end of render
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Krizek <mkrizek@redhat.com>
  • Loading branch information
davidism and mkrizek committed Apr 13, 2020
1 parent f1756a3 commit 00c768c
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Expand Up @@ -27,6 +27,10 @@ Unreleased
async environments. :issue:`1180`
- Fix whitespace being removed before tags in the middle of lines when
``lstrip_blocks`` is enabled. :issue:`1138`
- :class:`~nativetypes.NativeEnvironment` doesn't evaluate
intermediate strings during rendering. This prevents early
evaluation which could change the value of an expression.
:issue:`1186`


Version 2.11.1
Expand Down
31 changes: 7 additions & 24 deletions src/jinja2/nativetypes.py
@@ -1,4 +1,3 @@
import types
from ast import literal_eval
from itertools import chain
from itertools import islice
Expand All @@ -11,17 +10,14 @@
from .environment import Template


def native_concat(nodes, preserve_quotes=True):
def native_concat(nodes):
"""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 nodes: Iterable of nodes to concatenate.
:param preserve_quotes: Whether to re-wrap literal strings with
quotes, to preserve quotes around expressions for later parsing.
Should be ``False`` in :meth:`NativeEnvironment.render`.
"""
head = list(islice(nodes, 2))

Expand All @@ -31,37 +27,25 @@ def native_concat(nodes, preserve_quotes=True):
if len(head) == 1:
raw = head[0]
else:
if isinstance(nodes, types.GeneratorType):
nodes = chain(head, nodes)
raw = u"".join([text_type(v) for v in nodes])
raw = u"".join([text_type(v) for v in chain(head, nodes)])

try:
literal = literal_eval(raw)
return literal_eval(raw)
except (ValueError, SyntaxError, MemoryError):
return raw

# If literal_eval returned a string, re-wrap with the original
# quote character to avoid dropping quotes between expression nodes.
# Without this, "'{{ a }}', '{{ b }}'" results in "a, b", but should
# be ('a', 'b').
if preserve_quotes and isinstance(literal, str):
return "{quote}{}{quote}".format(literal, quote=raw[0])

return literal


class NativeCodeGenerator(CodeGenerator):
"""A code generator which renders Python types by not adding
``to_string()`` around output nodes, and using :func:`native_concat`
to convert complex strings back to Python types if possible.
``to_string()`` around output nodes.
"""

@staticmethod
def _default_finalize(value):
return value

def _output_const_repr(self, group):
return repr(native_concat(group))
return repr(u"".join([text_type(v) for v in group]))

def _output_child_to_const(self, node, frame, finalize):
const = node.as_const(frame.eval_ctx)
Expand Down Expand Up @@ -100,10 +84,9 @@ def render(self, *args, **kwargs):
Otherwise, the string is returned.
"""
vars = dict(*args, **kwargs)

try:
return native_concat(
self.root_render_func(self.new_context(vars)), preserve_quotes=False
)
return native_concat(self.root_render_func(self.new_context(vars)))
except Exception:
return self.environment.handle_exception()

Expand Down
11 changes: 11 additions & 0 deletions tests/test_nativetypes.py
Expand Up @@ -134,6 +134,17 @@ def test_concat_strings_with_quotes(env):
assert result == "--host='localhost' --user \"Jinja\""


def test_no_intermediate_eval(env):
from math import isclose

t = env.from_string("0.000{{ a }}")
result = t.render(a=7)
assert isinstance(result, float)
# If intermediate eval happened, 0.000 would render 0.0, then 7
# would be appended, resulting in 0.07.
assert isclose(result, 0.0007)


def test_spontaneous_env():
t = NativeTemplate("{{ true }}")
assert isinstance(t.environment, NativeEnvironment)

0 comments on commit 00c768c

Please sign in to comment.