Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
recursively convert nested dicts to nested namedtuples, giving you something like immutable object literals
from UserDict import IterableUserDict
import collections
__author__ = 'github.com/hangtwenty'
def tupperware(mapping):
""" Convert mappings to 'tupperwares' recursively.
Lets you use dicts like they're JavaScript Object Literals (~=JSON)...
It recursively turns mappings (dictionaries) into namedtuples.
Thus, you can cheaply create an object whose attributes are accessible
by dotted notation (all the way down).
Use cases:
* Fake objects (useful for dependency injection when you're making
fakes/stubs that are simpler than proper mocks)
* Storing data (like fixtures) in a structured way, in Python code
(data whose initial definition reads nicely like JSON). You could do
this with dictionaries, but namedtuples are immutable, and their
dotted notation can be clearer in some contexts.
.. doctest::
>>> t = tupperware({
... 'foo': 'bar',
... 'baz': {'qux': 'quux'},
... 'tito': {
... 'tata': 'tutu',
... 'totoro': 'tots',
... 'frobnicator': ['this', 'is', 'not', 'a', 'mapping']
... }
... })
>>> t # doctest: +ELLIPSIS
Tupperware(tito=Tupperware(...), foo='bar', baz=Tupperware(qux='quux'))
>>> t.tito # doctest: +ELLIPSIS
Tupperware(frobnicator=[...], tata='tutu', totoro='tots')
>>> t.tito.tata
'tutu'
>>> t.tito.frobnicator
['this', 'is', 'not', 'a', 'mapping']
>>> t.foo
'bar'
>>> t.baz.qux
'quux'
Args:
mapping: An object that might be a mapping. If it's a mapping, convert
it (and all of its contents that are mappings) to namedtuples
(called 'Tupperwares').
Returns:
A tupperware (a namedtuple (of namedtuples (of namedtuples (...)))).
If argument is not a mapping, it just returns it (this enables the
recursion).
"""
if (isinstance(mapping, collections.Mapping) and
not isinstance(mapping, ProtectedDict)):
for key, value in mapping.iteritems():
mapping[key] = tupperware(value)
return namedtuple_from_mapping(mapping)
return mapping
def namedtuple_from_mapping(mapping, name="Tupperware"):
this_namedtuple_maker = collections.namedtuple(name, mapping.iterkeys())
return this_namedtuple_maker(**mapping)
class ProtectedDict(IterableUserDict):
""" A class that exists just to tell `tupperware` not to eat it.
`tupperware` eats all dicts you give it, recursively; but what if you
actually want a dictionary in there? This will stop it. Just do
ProtectedDict({...}) or ProtectedDict(kwarg=foo).
"""
def tupperware_from_kwargs(**kwargs):
return tupperware(kwargs)
@cslarsen

This comment has been minimized.

Copy link

cslarsen commented Oct 14, 2015

I was looking for exactly something like this, thanks. You should really just wrap it up in a setup.py script and push it to PyPi, even though it's a very small snippet.

@brennv

This comment has been minimized.

Copy link

brennv commented Aug 22, 2016

Thank you, very helpful. I packaged it here... feedback welcome.

@vicngtor

This comment has been minimized.

Copy link

vicngtor commented Sep 19, 2016

Is there anyway to specify "parent" to point to one level above?

@abdulwahid24

This comment has been minimized.

Copy link

abdulwahid24 commented May 2, 2017

@brennv You did really great job. Appreciated

@pabloariasmora

This comment has been minimized.

Copy link

pabloariasmora commented Mar 2, 2018

Thanks for this work!

I was searching for the same, I required it on 3.6, so in case that somebody else requires it here is (basically I just update some references):

from collections import UserDict
import collections


__author__ = 'github.com/hangtwenty'


def tupperware(mapping):
    """ Convert mappings to 'tupperwares' recursively.
    Lets you use dicts like they're JavaScript Object Literals (~=JSON)...
    It recursively turns mappings (dictionaries) into namedtuples.
    Thus, you can cheaply create an object whose attributes are accessible
    by dotted notation (all the way down).
    Use cases:
        * Fake objects (useful for dependency injection when you're making
         fakes/stubs that are simpler than proper mocks)
        * Storing data (like fixtures) in a structured way, in Python code
        (data whose initial definition reads nicely like JSON). You could do
        this with dictionaries, but namedtuples are immutable, and their
        dotted notation can be clearer in some contexts.
    .. doctest::
        >>> t = tupperware({
        ...     'foo': 'bar',
        ...     'baz': {'qux': 'quux'},
        ...     'tito': {
        ...             'tata': 'tutu',
        ...             'totoro': 'tots',
        ...             'frobnicator': ['this', 'is', 'not', 'a', 'mapping']
        ...     }
        ... })
        >>> t # doctest: +ELLIPSIS
        Tupperware(tito=Tupperware(...), foo='bar', baz=Tupperware(qux='quux'))
        >>> t.tito # doctest: +ELLIPSIS
        Tupperware(frobnicator=[...], tata='tutu', totoro='tots')
        >>> t.tito.tata
        'tutu'
        >>> t.tito.frobnicator
        ['this', 'is', 'not', 'a', 'mapping']
        >>> t.foo
        'bar'
        >>> t.baz.qux
        'quux'
    Args:
        mapping: An object that might be a mapping. If it's a mapping, convert
        it (and all of its contents that are mappings) to namedtuples
        (called 'Tupperwares').
    Returns:
        A tupperware (a namedtuple (of namedtuples (of namedtuples (...)))).
        If argument is not a mapping, it just returns it (this enables the
        recursion).
    """

    if (isinstance(mapping, collections.Mapping) and
            not isinstance(mapping, ProtectedDict)):
        for key, value in mapping.items():
            mapping[key] = tupperware(value)
        return namedtuple_from_mapping(mapping)
    return mapping


def namedtuple_from_mapping(mapping, name="Tupperware"):
    this_namedtuple_maker = collections.namedtuple(name, mapping.keys())
    return this_namedtuple_maker(**mapping)


class ProtectedDict(UserDict):
    """ A class that exists just to tell `tupperware` not to eat it.
    `tupperware` eats all dicts you give it, recursively; but what if you
    actually want a dictionary in there? This will stop it. Just do
    ProtectedDict({...}) or ProtectedDict(kwarg=foo).
    """


def tupperware_from_kwargs(**kwargs):
    return tupperware(kwargs)

Again @hangtwenty, thanks!

@happydig

This comment has been minimized.

Copy link

happydig commented Dec 29, 2018

Thanks. I am searching for it too. From stackoverflow, copy and paste it here.

def tupleware(obj):
    if isinstance(obj, dict):
        fields = sorted(obj.keys())
        namedtuple_type = namedtuple(
            typename='TWare',
            field_names=fields,
            rename=True,
        )
        field_value_pairs = OrderedDict(
            (str(field), tupleware(obj[field])) for field in fields)
        try:
            return namedtuple_type(**field_value_pairs)
        except TypeError:
            # Cannot create namedtuple instance so fallback to dict (invalid attribute names)
            return dict(**field_value_pairs)
    elif isinstance(obj, (list, set, tuple, frozenset)):
        return [tupleware(item) for item in obj]
    else:
        return obj
@eliadh

This comment has been minimized.

Copy link

eliadh commented Nov 18, 2019

Great. very usefull. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.