summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2019-10-12 21:04:31 -0700
committerDavid Lord <davidism@gmail.com>2019-10-12 21:25:48 -0700
commit719537aeec37368e0d7fbea42a5d090bb5a1b7c9 (patch)
tree6ae8ae02ee249d7ab7af255e6523d198dbcc8fa7
parent636c7122d98f6ace18fc4860587ac3cc1b0bfb8e (diff)
downloadjinja2-no-finalize-template-data.tar.gz
don't finalize TemplateData nodesno-finalize-template-data
Finalize only applies to the output of expressions (constant or not). Add tests for context, eval, and env finalize functions.
-rw-r--r--.gitignore1
-rw-r--r--CHANGES.rst3
-rw-r--r--jinja2/compiler.py67
-rw-r--r--tests/test_api.py57
4 files changed, 88 insertions, 40 deletions
diff --git a/.gitignore b/.gitignore
index 8402c1d..81752e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@ venv-*/
.coverage.*
htmlcov
.pytest_cache/
+/.vscode/
diff --git a/CHANGES.rst b/CHANGES.rst
index 2c1e286..89844b2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -34,6 +34,9 @@ Unreleased
:issue:`755`, :pr:`938`
- Add new ``boolean``, ``false``, ``true``, ``integer`` and ``float``
tests. :pr:`824`
+- The environment's ``finalize`` function is only applied to the
+ output of expressions (constant or not), not static template data.
+ :issue:`63`
Version 2.10.3
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index c0eb240..708186b 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -1224,19 +1224,28 @@ class CodeGenerator(NodeVisitor):
if self.has_known_extends and frame.require_output_check:
return
+ finalize = text_type
+ finalize_src = None
allow_constant_finalize = True
+
if self.environment.finalize:
- func = self.environment.finalize
- if getattr(func, 'contextfunction', False) or \
- getattr(func, 'evalcontextfunction', False):
+ env_finalize = self.environment.finalize
+ finalize_src = "environment.finalize("
+
+ def finalize(value):
+ return text_type(env_finalize(value))
+
+ if getattr(env_finalize, "contextfunction", False):
+ finalize_src += "context, "
allow_constant_finalize = False
- elif getattr(func, 'environmentfunction', False):
- finalize = lambda x: text_type(
- self.environment.finalize(self.environment, x))
- else:
- finalize = lambda x: text_type(self.environment.finalize(x))
- else:
- finalize = text_type
+ elif getattr(env_finalize, "evalcontextfunction", False):
+ finalize_src += "context.eval_ctx, "
+ allow_constant_finalize = False
+ elif getattr(env_finalize, "environmentfunction", False):
+ finalize_src += "environment, "
+
+ def finalize(value):
+ return text_type(env_finalize(self.environment, value))
# if we are inside a frame that requires output checking, we do so
outdent_later = False
@@ -1250,27 +1259,37 @@ class CodeGenerator(NodeVisitor):
body = []
for child in node.nodes:
try:
- if not allow_constant_finalize:
+ # If the finalize function needs context, and this isn't
+ # template data, evaluate the node at render.
+ if not (
+ allow_constant_finalize
+ or isinstance(child, nodes.TemplateData)
+ ):
raise nodes.Impossible()
+
const = child.as_const(frame.eval_ctx)
except nodes.Impossible:
body.append(child)
continue
+
# the frame can't be volatile here, becaus otherwise the
# as_const() function would raise an Impossible exception
# at that point.
try:
if frame.eval_ctx.autoescape:
- if hasattr(const, '__html__'):
- const = const.__html__()
- else:
- const = escape(const)
- const = finalize(const)
+ const = escape(const)
+
+ # Only call finalize on expressions, not template data.
+ if isinstance(child, nodes.TemplateData):
+ const = text_type(const)
+ else:
+ const = finalize(const)
except Exception:
# if something goes wrong here we evaluate the node
# at runtime for easier debugging
body.append(child)
continue
+
if body and isinstance(body[-1], list):
body[-1].append(const)
else:
@@ -1306,10 +1325,7 @@ class CodeGenerator(NodeVisitor):
else:
self.write('to_string(')
if self.environment.finalize is not None:
- self.write('environment.finalize(')
- if getattr(self.environment.finalize,
- "contextfunction", False):
- self.write('context, ')
+ self.write(finalize_src)
close += 1
self.visit(item, frame)
self.write(')' * close)
@@ -1344,16 +1360,7 @@ class CodeGenerator(NodeVisitor):
self.write('escape(')
close += 1
if self.environment.finalize is not None:
- self.write('environment.finalize(')
- if getattr(self.environment.finalize,
- 'contextfunction', False):
- self.write('context, ')
- elif getattr(self.environment.finalize,
- 'evalcontextfunction', False):
- self.write('context.eval_ctx, ')
- elif getattr(self.environment.finalize,
- 'environmentfunction', False):
- self.write('environment, ')
+ self.write(finalize_src)
close += 1
self.visit(argument, frame)
self.write(')' * close + ', ')
diff --git a/tests/test_api.py b/tests/test_api.py
index 1829b1d..a457602 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -19,6 +19,9 @@ from jinja2 import Environment, Undefined, ChainableUndefined, \
from jinja2.compiler import CodeGenerator
from jinja2.runtime import Context
from jinja2.utils import Cycler
+from jinja2.utils import contextfunction
+from jinja2.utils import evalcontextfunction
+from jinja2.utils import environmentfunction
@pytest.mark.api
@@ -37,16 +40,50 @@ class TestExtendedAPI(object):
tmpl = env.from_string('{{ foo["items"] }}')
assert tmpl.render(foo={'items': 42}) == '42'
- def test_finalizer(self, env):
- def finalize_none_empty(value):
- if value is None:
- value = u''
- return value
- env = Environment(finalize=finalize_none_empty)
- tmpl = env.from_string('{% for item in seq %}|{{ item }}{% endfor %}')
- assert tmpl.render(seq=(None, 1, "foo")) == '||1|foo'
- tmpl = env.from_string('<{{ none }}>')
- assert tmpl.render() == '<>'
+ def test_finalize(self):
+ e = Environment(finalize=lambda v: "" if v is None else v)
+ t = e.from_string("{% for item in seq %}|{{ item }}{% endfor %}")
+ assert t.render(seq=(None, 1, "foo")) == "||1|foo"
+
+ def test_finalize_constant_expression(self):
+ e = Environment(finalize=lambda v: "" if v is None else v)
+ t = e.from_string("<{{ none }}>")
+ assert t.render() == "<>"
+
+ def test_no_finalize_template_data(self):
+ e = Environment(finalize=lambda v: type(v).__name__)
+ t = e.from_string("<{{ value }}>")
+ # If template data was finalized, it would print "strintstr".
+ assert t.render(value=123) == "<int>"
+
+ def test_context_finalize(self):
+ @contextfunction
+ def finalize(context, value):
+ return value * context["scale"]
+
+ e = Environment(finalize=finalize)
+ t = e.from_string("{{ value }}")
+ assert t.render(value=5, scale=3) == "15"
+
+ def test_eval_finalize(self):
+ @evalcontextfunction
+ def finalize(eval_ctx, value):
+ return str(eval_ctx.autoescape) + value
+
+ e = Environment(finalize=finalize, autoescape=True)
+ t = e.from_string("{{ value }}")
+ assert t.render(value="<script>") == "True&lt;script&gt;"
+
+ def test_env_autoescape(self):
+ @environmentfunction
+ def finalize(env, value):
+ return " ".join(
+ (env.variable_start_string, repr(value), env.variable_end_string)
+ )
+
+ e = Environment(finalize=finalize)
+ t = e.from_string("{{ value }}")
+ assert t.render(value="hello") == "{{ 'hello' }}"
def test_cycler(self, env):
items = 1, 2, 3