Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

native only evals at end of render #1190

Merged
merged 1 commit into from Apr 13, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
9 changes: 9 additions & 0 deletions tests/test_nativetypes.py
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)