123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- """
- Karmaworld Dictionary helpers
- Finals Club (c) 2014
- Author: Bryan Bonvallet
- """
- # I'd wager none of these are thread safe.
- import re
- from collections import MutableMapping
- needs_mapping = re.compile('{[a-zA-Z_]\w*}')
- class attrdict(object):
- """ Access dictionary by object attributes. """
- def __getattr__(self, attr):
- # create call stack, if not already there, to avoid loops
- try:
- callstack = object.__getattribute__(self, '__callstack')
- except AttributeError:
- object.__setattr__(self, '__callstack', [])
- callstack = object.__getattribute__(self, '__callstack')
- try:
- # don't call something already on the stack
- if attr in callstack:
- raise KeyError()
- # track this attribute before possibly recursing
- callstack.append(attr)
- retattr = self.__getitem__(attr)
- # success, remove attr from stack
- callstack.remove(attr)
- return retattr
- except KeyError:
- # if code is here, attr is not in dict
- try:
- # try to grab attr from the object
- retattr = super(attrdict, self).__getattribute__(attr)
- return retattr
- finally:
- # remove stack now that the attribute succeeded or failed
- callstack.remove(attr)
- def __setattr__(self, attr, value):
- self.__setitem__(attr, value)
- def __delattr__(self, attr):
- self.__delitem__(attr)
- class fallbackdict(MutableMapping, attrdict):
- """
- Retrieve default values from a fallback dictionary and dynamically .format
- """
- def __init__(self, fallback, *args, **kwargs):
- """
- Supply dictionary to fall back to when keys are missing.
- Other arguments are handled as normal dictionary.
- """
- # dodge attrdict problems by using object.__setattr__
- classname = type(self).__name__
- object.__setattr__(self, '_{0}__internaldict'.format(classname), {})
- object.__setattr__(self, '_{0}__fallback'.format(classname), fallback)
- object.__setattr__(self, '_{0}__fetching'.format(classname), [])
- super(fallbackdict, self).__init__(*args, **kwargs)
- def __needs_format__(self, value):
- """ Helper to determine when a string needs formatting. """
- return hasattr(value, 'format') and needs_mapping.search(value)
- def __getitem__(self, key):
- """
- Use this dict's value if it has one otherwise grab value from parent
- and populate any strings with format keyword arguments.
- """
- indict = self.__internaldict
- fetching = self.__fetching
- local = (key in indict)
- # this will throw a key error if the key isn't in either place
- # which is desirable
- value = indict[key] if local else self.__fallback[key]
- if (key in fetching) or (not self.__needs_format__(value)):
- # already seeking this key in a recursed call
- # or it doesn't need any formatting
- # so return as-is
- return value
- # if the code is this far, strings needs formatting
- # **self will call this function recursively.
- # prevent infinite recursion from e.g. d['recurse'] = '{recurse}'
- fetching.append(key)
- value = value.format(**self)
- # undo infinite recursion prevention
- fetching.remove(key)
- return value
- def __setitem__(self, key, value):
- """
- Set the internal dict with key/value.
- """
- self.__internaldict[key] = value
- def __delitem__(self, key):
- """ Delete the key from the internal dict. """
- del self.__internaldict[key]
- def __uniquekeys__(self):
- """ Returns unique keys between this dictionary and the fallback. """
- mykeys = set(self.__internaldict.keys())
- fbkeys = set(self.__fallback.keys())
- # set union: all keys from both, no repeats.
- return (mykeys | fbkeys)
- def __iter__(self):
- """ Returns the keys in this dictionary and the fallback. """
- return iter(self.__uniquekeys__())
- def __len__(self):
- """
- Returns the number of keys in this dictionary and the fallback.
- """
- return len(self.__uniquekeys__())
|