Jinja2 Changelog
================
-Version 2.9.1
+Version 2.9.2
-------------
(bugfix release, release date undecided)
- Fixed a regression that caused for loops to not be able to use the same
variable for the target as well as source iterator. (#640)
+- Add support for a previously unknown behavior of macros. It used to be
+ possible in some circumstances to explicitly provide a caller argument
+ to macros. While badly buggy and unintended it turns out that this is a
+ common case that gets copy pasted around. To not completely break backwards
+ compatibility with the most common cases it's now possible to provide an
+ explicit keyword argument for caller if it's given an explicit default.
Version 2.9.1
-------------
frame.symbols.analyze_node(node)
macro_ref = MacroRef(node)
+ explicit_caller = None
+ skip_special_params = set()
args = []
- for arg in node.args:
+ for idx, arg in enumerate(node.args):
+ if arg.name == 'caller':
+ explicit_caller = idx
+ if arg.name in ('kwargs', 'varargs'):
+ skip_special_params.add(arg.name)
args.append(frame.symbols.ref(arg.name))
undeclared = find_undeclared(node.body, ('caller', 'kwargs', 'varargs'))
+
if 'caller' in undeclared:
- args.append(frame.symbols.declare_parameter('caller'))
+ # In older Jinja2 versions there was a bug that allowed caller
+ # to retain the special behavior even if it was mentioned in
+ # the argument list. However thankfully this was only really
+ # working if it was the last argument. So we are explicitly
+ # checking this now and error out if it is anywhere else in
+ # the argument list.
+ if explicit_caller is not None:
+ try:
+ node.defaults[explicit_caller - len(node.args)]
+ except IndexError:
+ self.fail('When defining macros or call blocks the '
+ 'special "caller" argument must be omitted '
+ 'or be given a default.', node.lineno)
+ else:
+ args.append(frame.symbols.declare_parameter('caller'))
macro_ref.accesses_caller = True
- if 'kwargs' in undeclared:
+ if 'kwargs' in undeclared and not 'kwargs' in skip_special_params:
args.append(frame.symbols.declare_parameter('kwargs'))
macro_ref.accesses_kwargs = True
- if 'varargs' in undeclared:
+ if 'varargs' in undeclared and not 'varargs' in skip_special_params:
args.append(frame.symbols.declare_parameter('varargs'))
macro_ref.accesses_varargs = True
self.catch_kwargs = catch_kwargs
self.catch_varargs = catch_varargs
self.caller = caller
+ self.explicit_caller = 'caller' in arguments
if default_autoescape is None:
default_autoescape = environment.autoescape
self._default_autoescape = default_autoescape
arguments = list(args[:self._argument_count])
off = len(arguments)
+ # For information why this is necessary refer to the handling
+ # of caller in the `macro_body` handler in the compiler.
+ found_caller = False
+
# if the number of arguments consumed is not the number of
# arguments expected we start filling in keyword arguments
# and defaults.
value = kwargs.pop(name)
except KeyError:
value = missing
+ if name == 'caller':
+ found_caller = True
arguments.append(value)
+ else:
+ found_caller = self.explicit_caller
# it's important that the order of these arguments does not change
# if not also changed in the compiler's `function_scoping` method.
# the order is caller, keyword arguments, positional arguments!
- if self.caller:
+ if self.caller and not found_caller:
caller = kwargs.pop('caller', None)
if caller is None:
caller = self._environment.undefined('No caller defined',
name='caller')
arguments.append(caller)
+
if self.catch_kwargs:
arguments.append(kwargs)
elif kwargs:
+ if 'caller' in kwargs:
+ raise TypeError('macro %r was invoked with two values for '
+ 'the special caller argument. This is '
+ 'most likely a bug.' % self.name)
raise TypeError('macro %r takes no keyword argument %r' %
(self.name, next(iter(kwargs))))
if self.catch_varargs:
import pytest
from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
- TemplateNotFound, PrefixLoader
+ TemplateAssertionError, TemplateNotFound, PrefixLoader
from jinja2._compat import text_type
t = env.from_string('{% for x in x.y recursive %}{{ x }}{% endfor %}')
assert t.render(x={'y': [0, 1, 2]}) == '012'
+
+ def test_double_caller(self, env):
+ t = env.from_string('{% macro x(caller=none) %}[{% if caller %}'
+ '{{ caller() }}{% endif %}]{% endmacro %}'
+ '{{ x() }}{% call x() %}aha!{% endcall %}')
+ assert t.render() == '[][aha!]'
+
+ def test_double_caller_no_default(self, env):
+ with pytest.raises(TemplateAssertionError) as exc_info:
+ env.from_string('{% macro x(caller) %}[{% if caller %}'
+ '{{ caller() }}{% endif %}]{% endmacro %}')
+ assert exc_info.match(r'"caller" argument must be omitted or '
+ r'be given a default')
+
+ t = env.from_string('{% macro x(caller=none) %}[{% if caller %}'
+ '{{ caller() }}{% endif %}]{% endmacro %}')
+ with pytest.raises(TypeError) as exc_info:
+ t.module.x(None, caller=lambda: 42)
+ assert exc_info.match(r'\'x\' was invoked with two values for the '
+ r'special caller argument')