diff --git a/CHANGES b/CHANGES index 4b04cb7..d43cbd3 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,10 @@ 0.9.0 +- [bug] The Context.locals_() method becomes a private underscored + method, as this method has a specific internal use. The purpose + of Context.kwargs has been clarified, in that it only delivers + top level keyword arguments originally passed to template.render(). + [ticket:219] + - [bug] Fixed the babel plugin to properly interpret ${} sections inside of a "call" tag, i.e. <%self:some_tag attr="${_('foo')}"/>. Code that's subject to babel escapes in here needs to be diff --git a/doc/build/namespaces.rst b/doc/build/namespaces.rst index 078b15a..1453b80 100644 --- a/doc/build/namespaces.rst +++ b/doc/build/namespaces.rst @@ -245,9 +245,9 @@ So above, the body might be called as: ${self.body(5, y=10, someval=15, delta=7)} -The :class:`.Context` object also supplies a :attr:`~.Context.kwargs` accessor, for -cases when you'd like to pass along whatever is in the context to -a ``body()`` callable: +The :class:`.Context` object also supplies a :attr:`~.Context.kwargs` +accessor, for cases when you'd like to pass along the top level context +arguments to a ``body()`` callable: .. sourcecode:: mako diff --git a/mako/codegen.py b/mako/codegen.py index 0e377ec..971538c 100644 --- a/mako/codegen.py +++ b/mako/codegen.py @@ -14,7 +14,7 @@ from mako import compat -MAGIC_NUMBER = 8 +MAGIC_NUMBER = 9 # names which are hardwired into the # template and are not accessed via the @@ -548,7 +548,7 @@ def write_def_decl(self, node, identifiers): if not self.in_def and ( len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0): - nameargs.insert(0, 'context.locals_(__M_locals)') + nameargs.insert(0, 'context._locals(__M_locals)') else: nameargs.insert(0, 'context') self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls))) diff --git a/mako/runtime.py b/mako/runtime.py index 5c8cfe7..f94c109 100644 --- a/mako/runtime.py +++ b/mako/runtime.py @@ -58,8 +58,22 @@ def lookup(self): @property def kwargs(self): - """Return the dictionary of keyword arguments associated with this - :class:`.Context`. + """Return the dictionary of top level keyword arguments associated + with this :class:`.Context`. + + This dictionary only includes the top-level arguments passed to + :meth:`.Template.render`. It does not include names produced within + the template execution such as local variable names or special names + such as ``self``, ``next``, etc. + + The purpose of this dictionary is primarily for the case that + a :class:`.Template` accepts arguments via its ``<%page>`` tag, + which are normally expected to be passed via :meth:`.Template.render`, + except the template is being called in an inheritance context, + using the ``body()`` method. :attr:`.Context.kwargs` can then be + used to propagate these arguments to the inheriting template:: + + ${next.body(**context.kwargs)} """ return self._kwargs.copy() @@ -144,11 +158,18 @@ def _copy(self): c.caller_stack = self.caller_stack return c - def locals_(self, d): + def _locals(self, d): """Create a new :class:`.Context` with a copy of this - :class:`.Context`'s current state, updated with the given dictionary.""" + :class:`.Context`'s current state, + updated with the given dictionary. + + The :attr:`.Context.kwargs` collection remains + unaffected. + - if len(d) == 0: + """ + + if not d: return self c = self._copy() c._data.update(d) @@ -173,19 +194,22 @@ def __nonzero__(self): return self.__bool__() def __bool__(self): - return self._get_caller() and True or False + return len(self) and self._get_caller() and True or False def _get_caller(self): # this method can be removed once # codegen MAGIC_NUMBER moves past 7 return self[-1] + def __getattr__(self, key): return getattr(self._get_caller(), key) + def _push_frame(self): frame = self.nextcaller or None self.append(frame) self.nextcaller = None return frame + def _pop_frame(self): self.nextcaller = self.pop() @@ -721,10 +745,10 @@ def _inherit_from(context, uri, calling_uri): ih = self_ns while ih.inherits is not None: ih = ih.inherits - lclcontext = context.locals_({'next':ih}) + lclcontext = context._locals({'next': ih}) ih.inherits = TemplateNamespace("self:%s" % template.uri, lclcontext, - template = template, + template=template, populate_self=False) context._data['parent'] = lclcontext._data['local'] = ih.inherits callable_ = getattr(template.module, '_mako_inherit', None) diff --git a/test/test_runtime.py b/test/test_runtime.py new file mode 100644 index 0000000..af7dbee --- /dev/null +++ b/test/test_runtime.py @@ -0,0 +1,21 @@ +"""Assorted runtime unit tests +""" +from mako import runtime +import unittest +from . import eq_ + +class ContextTest(unittest.TestCase): + def test_locals_kwargs(self): + c = runtime.Context(None, foo='bar') + eq_(c.kwargs, {'foo': 'bar'}) + + d = c._locals({'zig': 'zag'}) + + # kwargs is the original args sent to the Context, + # it's intentionally kept separate from _data + eq_(c.kwargs, {'foo': 'bar'}) + eq_(d.kwargs, {'foo': 'bar'}) + + eq_(d._data['zig'], 'zag') + +