Skip to content

Support mappings that are not simply dicts. #144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions pystache/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@
stack elements: hashes and objects. For the purposes of interpreting the
spec, we define these categories mutually exclusively as follows:

(1) Hash: an item whose type is a subclass of dict.
(1) Hash: an item whose type is a subclass of collections.Mapping.

.. note::

collections.Mapping is used instead of a dict in order to allow other
mapping types to be valid contexts. See
`Issue 144 <https://github.com/defunkt/pystache/pull/144>`

(2) Object: an item that is neither a hash nor an instance of a
built-in type.

"""

import collections

from pystache.common import PystacheError


Expand Down Expand Up @@ -43,8 +51,11 @@ def _get_value(context, key):
The ContextStack.get() docstring documents this function's intended behavior.

"""
if isinstance(context, dict):
if isinstance(context, collections.Mapping):
# Then we consider the argument a "hash" for the purposes of the spec.
# Using collections.Mapping allows other mapping types to be registered
# as valid "hash" contexts. See
# `Issue 144 <https://github.com/defunkt/pystache/pull/144>`
#
# We do a membership test to avoid using exceptions for flow control
# (e.g. catching KeyError).
Expand Down
46 changes: 46 additions & 0 deletions pystache/tests/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from datetime import datetime
import unittest
import collections

from pystache.context import _NOT_FOUND, _get_value, KeyNotFoundError, ContextStack
from pystache.tests.common import AssertIsMixin, AssertStringMixin, AssertExceptionMixin, Attachable
Expand Down Expand Up @@ -102,6 +103,51 @@ class DictSubclass(dict): pass

self.assertEqual(_get_value(item, "foo"), "bar")

def test_dictionary__mapping_implementation(self):
"""
Test that registered implementations of collections.Mapping are treated as dictionaries.

See https://github.com/defunkt/pystache/pull/144

"""
class MappingSubclass(collections.Mapping):

def __init__(self, *args, **kwargs):
self._mapping = dict(*args, **kwargs)
super(MappingSubclass, self).__init__()

def __getitem__(self, key):
return self._mapping[key]

def __iter__(self):
return iter(self._mapping)

def __len__(self):
return len(self._mapping)

class RegisteredMapping(object):

def __init__(self, *args, **kwargs):
self._mapping = dict(*args, **kwargs)
super(RegisteredMapping, self).__init__()

def __getitem__(self, key):
return self._mapping[key]

def __iter__(self):
return iter(self._mapping)

def __len__(self):
return len(self._mapping)

collections.Mapping.register(RegisteredMapping)

item = MappingSubclass(foo="bar")
self.assertEqual(_get_value(item, "foo"), "bar")

item = RegisteredMapping(foo="bar")
self.assertEqual(_get_value(item, "foo"), "bar")

### Case: the item is an object.

def test_object__attribute_present(self):
Expand Down