Skip to content

Commit

Permalink
Merge pull request #1190 from pallets/native-eval
Browse files Browse the repository at this point in the history
native only evals at end of render
  • Loading branch information
davidism authored Apr 13, 2020
2 parents f1756a3 + f75cb42 commit 179df6b
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 24 deletions.
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)

0 comments on commit 179df6b

Please sign in to comment.