Commit 7492f81a by Chris Jerdonek

Refactored Renderer._make_context() to use a Context.create() method.

parent c9cb0c53
...@@ -67,6 +67,11 @@ class Context(object): ...@@ -67,6 +67,11 @@ class Context(object):
Querying the stack for the value of a key queries the items in the Querying the stack for the value of a key queries the items in the
stack in order from last-added objects to first (last in, first out). stack in order from last-added objects to first (last in, first out).
*Caution*:
This class currently does not support recursive nesting in that
items in the stack cannot themselves be Context instances.
See the docstrings of the methods of this class for more information. See the docstrings of the methods of this class for more information.
""" """
...@@ -99,9 +104,70 @@ class Context(object): ...@@ -99,9 +104,70 @@ class Context(object):
In particular, an item can be an ordinary object with no In particular, an item can be an ordinary object with no
mapping-like characteristics. mapping-like characteristics.
*Caution*:
Items should not themselves be Context instances, as recursive
nesting does not behave as one might expect.
""" """
self._stack = list(items) self._stack = list(items)
@staticmethod
def create(*context, **kwargs):
"""
Build a Context instance from a sequence of "mapping-like" objects.
This factory-style method is more general than the Context class's
constructor in that Context instances can themselves appear in the
argument list. This is not true of the constructor.
Here is an example illustrating various aspects of this method:
>>> obj1 = {'animal': 'cat', 'vegetable': 'carrot', 'mineral': 'copper'}
>>> obj2 = Context({'vegetable': 'spinach', 'mineral': 'silver'})
>>>
>>> context = Context.create(obj1, None, obj2, mineral='gold')
>>>
>>> context.get('animal')
'cat'
>>> context.get('vegetable')
'spinach'
>>> context.get('mineral')
'gold'
Arguments:
*context: zero or more dictionaries, Context instances, or objects
with which to populate the initial context stack. None
arguments will be skipped. Items in the *context list are
added to the stack in order so that later items in the argument
list take precedence over earlier items. This behavior is the
same as the constructor's.
**kwargs: additional key-value data to add to the context stack.
As these arguments appear after all items in the *context list,
in the case of key conflicts these values take precedence over
all items in the *context list. This behavior is the same as
the constructor's.
"""
items = context
context = Context()
for item in items:
if item is None:
continue
if isinstance(item, Context):
context._stack.extend(item._stack)
else:
context.push(item)
if kwargs:
context.push(kwargs)
return context
def get(self, key, default=None): def get(self, key, default=None):
""" """
Query the stack for the given key, and return the resulting value. Query the stack for the given key, and return the resulting value.
......
...@@ -167,24 +167,6 @@ class Renderer(object): ...@@ -167,24 +167,6 @@ class Renderer(object):
# the default_encoding and decode_errors attributes. # the default_encoding and decode_errors attributes.
return unicode(s, self.default_encoding, self.decode_errors) return unicode(s, self.default_encoding, self.decode_errors)
def _make_context(self, context, **kwargs):
"""
Initialize the context attribute.
"""
if context is None:
context = {}
if isinstance(context, Context):
context = context.copy()
else:
context = Context(context)
if kwargs:
context.push(kwargs)
return context
def _make_reader(self): def _make_reader(self):
""" """
Create a Reader instance using current attributes. Create a Reader instance using current attributes.
...@@ -262,7 +244,7 @@ class Renderer(object): ...@@ -262,7 +244,7 @@ class Renderer(object):
loader = self._make_loader() loader = self._make_loader()
return loader.get(template_name) return loader.get(template_name)
def render_path(self, template_path, context=None, **kwargs): def render_path(self, template_path, *context, **kwargs):
""" """
Render the template at the given path using the given context. Render the template at the given path using the given context.
...@@ -270,9 +252,9 @@ class Renderer(object): ...@@ -270,9 +252,9 @@ class Renderer(object):
""" """
template = self.read(template_path) template = self.read(template_path)
return self.render(template, context, **kwargs) return self.render(template, *context, **kwargs)
def render(self, template, context=None, **kwargs): def render(self, template, *context, **kwargs):
""" """
Render the given template using the given context. Render the given template using the given context.
...@@ -285,15 +267,20 @@ class Renderer(object): ...@@ -285,15 +267,20 @@ class Renderer(object):
using this instance's default_encoding and decode_errors using this instance's default_encoding and decode_errors
attributes. See the constructor docstring for more information. attributes. See the constructor docstring for more information.
context: a dictionary, Context, or object (e.g. a View instance). *context: zero or more dictionaries, Context instances, or objects
with which to populate the initial context stack. None
arguments are skipped. Items in the *context list are added to
the context stack in order so that later items in the argument
list take precedence over earlier items.
**kwargs: additional key values to add to the context when **kwargs: additional key-value data to add to the context stack.
rendering. These values take precedence over the context on As these arguments appear after all items in the *context list,
any key conflicts. in the case of key conflicts these values take precedence over
all items in the *context list.
""" """
engine = self._make_render_engine() engine = self._make_render_engine()
context = self._make_context(context, **kwargs) context = Context.create(*context, **kwargs)
# RenderEngine.render() requires that the template string be unicode. # RenderEngine.render() requires that the template string be unicode.
template = self._to_unicode_hard(template) template = self._to_unicode_hard(template)
......
...@@ -168,7 +168,7 @@ class GetItemTestCase(TestCase): ...@@ -168,7 +168,7 @@ class GetItemTestCase(TestCase):
self.assertRaises(AttributeError, _get_item, obj, "foo") self.assertRaises(AttributeError, _get_item, obj, "foo")
class ContextTestCase(TestCase): class ContextTests(TestCase):
""" """
Test the Context class. Test the Context class.
...@@ -189,6 +189,67 @@ class ContextTestCase(TestCase): ...@@ -189,6 +189,67 @@ class ContextTestCase(TestCase):
""" """
context = Context({}, {}, {}) context = Context({}, {}, {})
## Test the static create() method.
def test_create__dictionary(self):
"""
Test passing a dictionary.
"""
context = Context.create({'foo': 'bar'})
self.assertEquals(context.get('foo'), 'bar')
def test_create__none(self):
"""
Test passing None.
"""
context = Context.create({'foo': 'bar'}, None)
self.assertEquals(context.get('foo'), 'bar')
def test_create__object(self):
"""
Test passing an object.
"""
class Foo(object):
foo = 'bar'
context = Context.create(Foo())
self.assertEquals(context.get('foo'), 'bar')
def test_create__context(self):
"""
Test passing a Context instance.
"""
obj = Context({'foo': 'bar'})
context = Context.create(obj)
self.assertEquals(context.get('foo'), 'bar')
def test_create__kwarg(self):
"""
Test passing a keyword argument.
"""
context = Context.create(foo='bar')
self.assertEquals(context.get('foo'), 'bar')
def test_create__precedence_positional(self):
"""
Test precedence of positional arguments.
"""
context = Context.create({'foo': 'bar'}, {'foo': 'buzz'})
self.assertEquals(context.get('foo'), 'buzz')
def test_create__precedence_keyword(self):
"""
Test precedence of keyword arguments.
"""
context = Context.create({'foo': 'bar'}, foo='buzz')
self.assertEquals(context.get('foo'), 'buzz')
def test_get__key_present(self): def test_get__key_present(self):
""" """
Test getting a key. Test getting a key.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment