^jinja2/.*\.so$
^docs/_build
^(build|dist|Jinja2\.egg-info)/
-\.py[co]$
+\.py[cod]$
\$py\.class$
\.DS_Store$
^j?env/
Jinja2 Changelog
================
-Version 2.2.2
--------------
-(bugfix release, to be released soon)
+Version 2.3
+-----------
+(codename Gnok, release date to be selected.)
- fixes issue with code generator that causes unbound variables
to be generated if set was used in if-blocks.
+- added a deprecation warning for a variable assignment, scope bug
+ that exists since 2.0, code could depend on. See :ref:`jinja-scoping-bug`
+ for more information on this problem.
Version 2.2.1
-------------
Python 2.3 support you either have to use `Jinja 1`_ or other templating
engines that still support 2.3.
+.. _jinja-scoping-bug:
+
+Scoping Bug in Jinja2
+---------------------
+
+Jinja2 currently has a scoping bug that causes confusing behavior. If you
+have a layout template that defines a macro, and a child template that
+does the same thing, it will see the macro from the layout template:
+
+layout.tmpl:
+
+.. sourcecode:: jinja
+
+ {% macro foo() %}LAYOUT{% endmacro %}
+ {% block body %}{% endblock %}
+
+child.tmpl:
+
+.. sourcecode:: jinja
+
+ {% extends 'layout.tmpl' %}
+ {% macro foo() %}CHILD{% endmacro %}
+ {% block body %}{{ foo() }}{% endblock %}
+
+This will print ``LAYOUT`` in Jinja2 versions older than 2.5. Starting
+with Jinja 2.2.2, there will however be a deprecation warning for this
+behavior. Starting with Jinja 2.5, this behavior will change so that
+``CHILD`` is printed instead.
+
+The reason why it was not changed right away is that some templates could
+depend on macros or variables defined in the layout template to be
+available in a child template. This however was undocumented behavior and
+is considered a bug.
+
+If you depend on this behavior, move your macros into a different file and
+import it into the layout and child template.
+
.. _Jinja 1: http://jinja.pocoo.org/1/
# overhead by just not processing any inheritance code.
have_extends = node.find(nodes.Extends) is not None
+ # are there any block tags? If yes, we need a copy of the scope.
+ have_blocks = node.find(nodes.Block) is not None
+
# find all blocks
for block in node.find_all(nodes.Block):
if block.name in self.blocks:
self.indent()
if have_extends:
self.writeline('parent_template = None')
+ if have_blocks:
+ self.writeline('block_context = context._block()')
if 'self' in find_undeclared(node.body, ('self',)):
frame.identifiers.add_special('self')
self.writeline('l_self = TemplateReference(context)')
if 'self' in undeclared:
block_frame.identifiers.add_special('self')
self.writeline('l_self = TemplateReference(context)')
+ if block.find(nodes.Block) is not None:
+ self.writeline('block_context = context._block(%r)' % name)
if 'super' in undeclared:
block_frame.identifiers.add_special('super')
self.writeline('l_super = context.super(%r, '
self.indent()
level += 1
if node.scoped:
- context = 'context.derived(locals())'
+ context = 'block_context.derived(locals())'
else:
- context = 'context'
+ context = 'block_context'
self.writeline('for event in context.blocks[%r][0](%s):' % (
node.name, context), node)
self.indent()
context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems())
return context
+ def _block(self, block=None):
+ """Creates a context that is used for block execution. Currently this
+ returns a special `_BlockContext` that warns about changed behavior.
+ In Jinja 2.5, this will instead just return a new context with the same
+ resolve behavior. Do not call from anywhere but the generated code!
+
+ :private:
+ """
+ return _BlockContext(self, block)
+
def _all(meth):
proxy = lambda self: getattr(self.get_all(), meth)()
proxy.__doc__ = getattr(dict, meth).__doc__
)
+class _BlockContext(Context):
+ """Implements a deprecation warning for the changed block assignments."""
+ __slots__ = ('real_context', 'block_name')
+
+ def __init__(self, real_context, block_name):
+ super(_BlockContext, self).__init__(real_context.environment,
+ real_context.parent,
+ real_context.name, {})
+ self.vars = dict(real_context.vars)
+ self.exported_vars = set(real_context.exported_vars)
+ self.blocks = real_context.blocks
+ self.real_context = real_context
+ self.block_name = block_name
+
+ def resolve(self, key):
+ self_rv = super(_BlockContext, self).resolve(key)
+ base_rv = self.real_context.resolve(key)
+ if self_rv != base_rv and not isinstance(base_rv, Undefined):
+ from warnings import warn
+ if self.block_name is not None:
+ detail = 'accessing %r from inside block %r' \
+ % (key, self.block_name)
+ else:
+ detail = 'accessing %r toplevel' % key
+ detail += ', in template %s' % self.name
+ warn(DeprecationWarning('variables set in a base template '
+ 'will no longer leak into the child '
+ 'context in future versions. Happened '
+ 'when ' + detail))
+ return base_rv
+ return self_rv
+
+
# register the context as mapping if possible
try:
from collections import Mapping
setup(
name='Jinja2',
- version='2.2.2',
+ version='2.3',
url='http://jinja.pocoo.org/',
license='BSD',
author='Armin Ronacher',
env = Environment()
+from nose import SkipTest
from nose.tools import assert_raises
tmpl = env.from_string('{% if b %}{% set a = 42 %}{% endif %}{{ a }}')
assert tmpl.render(a=23) == '23'
assert tmpl.render(b=True) == '42'
+
+
+def test_local_macros_first():
+ raise SkipTest('Behavior will change in 2.3')
+ env = Environment(loader=DictLoader({
+ 'layout.html': ('{% macro foo() %}LAYOUT{% endmacro %}'
+ '{% block body %}{% endblock %}'),
+ 'child.html': ('{% extends "layout.html" %}'
+ '{% macro foo() %}CHILD{% endmacro %}'
+ '{% block body %}{{ foo() }}{% endblock %}')
+ }))
+ assert env.get_template('child.html').render() == 'CHILD'