Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions tests/test_nativetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ def test_concat_strings_with_quotes(env):
assert result == "--host='localhost' --user \"Jinja\""


def test_no_intermediate_eval(env):
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 result < 0.007 # TODO use math.isclose in Python 3


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