diff --git a/CHANGES.rst b/CHANGES.rst index e6608f458..495f693e4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 diff --git a/src/jinja2/nativetypes.py b/src/jinja2/nativetypes.py index 9866c962d..a9ead4e2b 100644 --- a/src/jinja2/nativetypes.py +++ b/src/jinja2/nativetypes.py @@ -1,4 +1,3 @@ -import types from ast import literal_eval from itertools import chain from itertools import islice @@ -11,7 +10,7 @@ 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 @@ -19,9 +18,6 @@ def native_concat(nodes, preserve_quotes=True): 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)) @@ -31,29 +27,17 @@ 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 @@ -61,7 +45,7 @@ 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) @@ -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() diff --git a/tests/test_nativetypes.py b/tests/test_nativetypes.py index 77d378d27..76871ab5d 100644 --- a/tests/test_nativetypes.py +++ b/tests/test_nativetypes.py @@ -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)