# Buggy code
def hash_params(params):
    if not isinstance(params, dict):
        if isinstance(params, list):
            return frozenset(params)
        else:
            return params
    else:
        s = set()
        for k, v in iteritems(params):
            if isinstance(v, dict):
                s.update((k, hash_params(v)))
            elif isinstance(v, list):
                things = []
                for item in v:
                    things.append(hash_params(item))
                s.update((k, tuple(things)))
            else:
                s.update((k, v))
        return frozenset(s)


# Traceback (TypeError)
TASK [include_role] ************************************************************
task path: /Users/ahaines/work/iot/infrastructure/test.yml:3
ERROR! Unexpected Exception: unhashable type: 'dict'
the full traceback was:

Traceback (most recent call last):
File "/usr/local/bin/ansible-playbook", line 103, in <module>
exit_code = cli.run()
File "/usr/local/Cellar/ansible/2.2.0.0_1/libexec/lib/python2.7/site-packages/ansible/cli/playbook.py", line 159, in run
results = pbex.run()
File "/usr/local/Cellar/ansible/2.2.0.0_1/libexec/lib/python2.7/site-packages/ansible/executor/playbook_executor.py", line 154, in run
result = self._tqm.run(play=play)
File "/usr/local/Cellar/ansible/2.2.0.0_1/libexec/lib/python2.7/site-packages/ansible/executor/task_queue_manager.py", line 282, in run
play_return = strategy.run(iterator, play_context)
File "/usr/local/Cellar/ansible/2.2.0.0_1/libexec/lib/python2.7/site-packages/ansible/plugins/strategy/linear.py", line 304, in run
all_role_blocks.extend(new_ir.get_block_list(play=iterator._play, variable_manager=self._variable_manager, loader=self._loader))
File "/usr/local/Cellar/ansible/2.2.0.0_1/libexec/lib/python2.7/site-packages/ansible/playbook/role_include.py", line 76, in get_block_list
actual_role = Role.load(ri, myplay, parent_role=self._parent_role, from_files=self._from_files)
File "/usr/local/Cellar/ansible/2.2.0.0_1/libexec/lib/python2.7/site-packages/ansible/playbook/role/__init__.py", line 118, in load
hashed_params = hash_params(params)
File "/usr/local/Cellar/ansible/2.2.0.0_1/libexec/lib/python2.7/site-packages/ansible/playbook/role/__init__.py", line 54, in hash_params
s.update((k, hash_params(v)))
File "/usr/local/Cellar/ansible/2.2.0.0_1/libexec/lib/python2.7/site-packages/ansible/playbook/role/__init__.py", line 61, in hash_params
s.update((k, v))
TypeError: unhashable type: 'dict'

# Fixed code
def hash_params(params):
    """
    Construct a data structure of parameters that is hashable.

    This requires changing any mutable data structures into immutable ones.
    We chose a frozenset because role parameters have to be unique.

    .. warning::  this does not handle unhashable scalars.  Two things
        mitigate that limitation:

        1) There shouldn't be any unhashable scalars specified in the yaml
        2) Our only choice would be to return an error anyway.
    """
    # Any container is unhashable if it contains unhashable items (for
    # instance, tuple() is a Hashable subclass but if it contains a dict, it
    # cannot be hashed)
    if isinstance(params, collections.Container) and not isinstance(
        params, (text_type, binary_type)
    ):
        if isinstance(params, collections.Mapping):
            try:
                # Optimistically hope the contents are all hashable
                new_params = frozenset(params.items())
            except TypeError:
                new_params = set()
                for k, v in params.items():
                    # Hash each entry individually
                    new_params.update((k, hash_params(v)))
                new_params = frozenset(new_params)

        elif isinstance(params, (collections.Set, collections.Sequence)):
            try:
                # Optimistically hope the contents are all hashable
                new_params = frozenset(params)
            except TypeError:
                new_params = set()
                for v in params:
                    # Hash each entry individually
                    new_params.update(hash_params(v))
                new_params = frozenset(new_params)
        else:
            # This is just a guess.
            new_params = frozenset(params)
        return new_params

    # Note: We do not handle unhashable scalars but our only choice would be
    # to raise an error there anyway.
    return frozenset((params,))