Merge pull request #2 from pjenvey/master

simplify
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 @@
 
     ${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 @@
         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 @@
 
     @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 @@
         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.
 
-        if len(d) == 0:
+        The :attr:`.Context.kwargs` collection remains
+        unaffected.
+
+
+        """
+
+        if not d:
             return self
         c = self._copy()
         c._data.update(d)
@@ -173,19 +194,22 @@
         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 @@
     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')
+
+