2 Karmaworld Dictionary helpers
4 Author: Bryan Bonvallet
7 # I'd wager none of these are thread safe.
10 from collections import MutableMapping
13 needs_mapping = re.compile('{[a-zA-Z_]\w*}')
16 class attrdict(object):
17 """ Access dictionary by object attributes. """
18 def __getattr__(self, attr):
19 # create call stack, if not already there, to avoid loops
21 callstack = object.__getattribute__(self, '__callstack')
22 except AttributeError:
23 object.__setattr__(self, '__callstack', [])
24 callstack = object.__getattribute__(self, '__callstack')
27 # don't call something already on the stack
30 # track this attribute before possibly recursing
31 callstack.append(attr)
32 retattr = self.__getitem__(attr)
33 # success, remove attr from stack
34 callstack.remove(attr)
37 # if code is here, attr is not in dict
39 # try to grab attr from the object
40 retattr = super(attrdict, self).__getattribute__(attr)
43 # remove stack now that the attribute succeeded or failed
44 callstack.remove(attr)
46 def __setattr__(self, attr, value):
47 self.__setitem__(attr, value)
49 def __delattr__(self, attr):
50 self.__delitem__(attr)
53 class fallbackdict(MutableMapping, attrdict):
55 Retrieve default values from a fallback dictionary and dynamically .format
57 def __init__(self, fallback, *args, **kwargs):
59 Supply dictionary to fall back to when keys are missing.
60 Other arguments are handled as normal dictionary.
62 # dodge attrdict problems by using object.__setattr__
63 classname = type(self).__name__
64 object.__setattr__(self, '_{0}__internaldict'.format(classname), {})
65 object.__setattr__(self, '_{0}__fallback'.format(classname), fallback)
66 object.__setattr__(self, '_{0}__fetching'.format(classname), [])
67 super(fallbackdict, self).__init__(*args, **kwargs)
69 def __needs_format__(self, value):
70 """ Helper to determine when a string needs formatting. """
71 return hasattr(value, 'format') and needs_mapping.search(value)
73 def __getitem__(self, key):
75 Use this dict's value if it has one otherwise grab value from parent
76 and populate any strings with format keyword arguments.
78 indict = self.__internaldict
79 fetching = self.__fetching
81 local = (key in indict)
82 # this will throw a key error if the key isn't in either place
84 value = indict[key] if local else self.__fallback[key]
86 if (key in fetching) or (not self.__needs_format__(value)):
87 # already seeking this key in a recursed call
88 # or it doesn't need any formatting
92 # if the code is this far, strings needs formatting
93 # **self will call this function recursively.
94 # prevent infinite recursion from e.g. d['recurse'] = '{recurse}'
96 value = value.format(**self)
97 # undo infinite recursion prevention
101 def __setitem__(self, key, value):
103 Set the internal dict with key/value.
105 self.__internaldict[key] = value
107 def __delitem__(self, key):
108 """ Delete the key from the internal dict. """
109 del self.__internaldict[key]
111 def __uniquekeys__(self):
112 """ Returns unique keys between this dictionary and the fallback. """
113 mykeys = set(self.__internaldict.keys())
114 fbkeys = set(self.__fallback.keys())
115 # set union: all keys from both, no repeats.
116 return (mykeys | fbkeys)
119 """ Returns the keys in this dictionary and the fallback. """
120 return iter(self.__uniquekeys__())
124 Returns the number of keys in this dictionary and the fallback.
126 return len(self.__uniquekeys__())