don't finalize TemplateData nodes

Finalize only applies to the output of expressions (constant or not).
Add tests for context, eval, and env finalize functions.
This commit is contained in:
David Lord 2019-10-12 21:04:31 -07:00
parent 636c7122d9
commit 719537aeec
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
4 changed files with 88 additions and 40 deletions

1
.gitignore vendored
View File

@ -17,3 +17,4 @@ venv-*/
.coverage.*
htmlcov
.pytest_cache/
/.vscode/

View File

@ -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

View File

@ -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 + ', ')

View File

@ -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