Source code for bl.dict

# definition of a dict-replacement class that allows dot-notation attribute access

class Dict(dict):
    """A dict class that:
        * allows dot-notation attribute access, returning None if key not found
        * sorts keys on calls to keys() and items() and repr(), making many things easier
        * therefore requires that all keys need to be a sortable collection -- 
            for example only use string keys.
        * allows calling itself with parameters, which creates a new Dict based on 
            this one without modifying it. (immutability)
    Usage:
    >>> d = Dict(c=3, b=2, a=1)                     # initialize with parameters or **{...}
    >>> d.a                                         # dot-access notation
    1
    >>> d                                           # ordered keys
    {'a': 1, 'b': 2, 'c': 3}
    >>> d.update(d=4); d                            # Dict.update() modifies Dict in place
    {'a': 1, 'b': 2, 'c': 3, 'd': 4}
    >>> d(e=5)                                      # new Dict is a copy, doesn't modify existing
    {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
    >>> d                                           # no changes
    {'a': 1, 'b': 2, 'c': 3, 'd': 4}
    """

    def __init__(xCqNck7t, **kwargs):
        # The random name for self here is necessary to avoid key conflicts with "self"
        # (used as a key, e.g., in the WordPress API).
        dict.__init__(xCqNck7t)
        xCqNck7t.update(**kwargs)        

    def __call__(xCqNck7t, *args, **kwargs):
        """Dict(key=val) creates a new dict with the key:val. That has always been true for 
        initializing a new Dict. But now, this pattern also works for an existing Dict. 
        For example:
        >>> d1 = Dict(a=1, b=2); d2 = d1(c=3); d1; d2
        {'a': 1, 'b': 2}
        {'a': 1, 'b': 2, 'c': 3}
        >>> 
        Note that d1 has not been changed — this method creates a new dict based
        on the old one without changing the old one.
        """
        d = xCqNck7t.__class__(*args, **xCqNck7t)
        d.update(**kwargs)
        return d

    def update(xCqNck7t, **kwargs):
        """Updates the Dict with the given values. Turns internal dicts into Dicts."""
        def dict_list_val(inlist):
            l = []
            for i in inlist:
                if type(i)==dict:
                    l.append(Dict(**i))
                elif type(i)==list:
                    l.append(make_list(i))
                elif type(i)==bytes:
                    l.append(i.decode('UTF-8'))
                else:
                    l.append(i)
            return l
        for k in list(kwargs.keys()):
            if type(kwargs[k])==dict:
                xCqNck7t[k] = Dict(**kwargs[k])
            elif type(kwargs[k])==list:
                xCqNck7t[k] = dict_list_val(kwargs[k])
            else:
                xCqNck7t[k] = kwargs[k]

    # allow dot-notation attribute access alongside dict-notation
    def __getattr__(self, name):
        return dict.get(self, name)       # returns None if the key is not found.

    def __setattr__(self, name, val):
        self[name] = val

    def __repr__(self):
        """displays the Dict with keys in alphabetical order, for consistent test output."""
        keys = self.keys()
        keys.sort()
        return "{" + ", ".join(["%s: %s" % (repr(k), repr(self[k])) for k in keys]) + "}"

    def keys(self, key=None, reverse=False):
        """sort the keys before returning them"""
        ks = sorted(list(dict.keys(self)), key=key, reverse=reverse)
        return ks
        
    def values(self, key=None, reverse=False):
        """sort the values in the same order as the keys"""
        ks = self.keys(key=key, reverse=reverse)
        return [self[k] for k in ks]

    def json(self, indent=None):
        import json as _json
        return _json.dumps(self, indent=indent)
  
class StringDict(Dict):
    """Returns "" when a key does not exist. This is useful in web apps and 
    where a value is going to be built based on conditions, without needing to
    initialize the value first. For example:
    >>> sd = StringDict()
    >>> sd.name += "A Dict"     # Able to use += here.
    """
    def __getattr__(self, name):
        return Dict.get(self, name) or ""

if __name__ == "__main__":
    import doctest
    doctest.testmod()