dicthelpers.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """
  2. Karmaworld Dictionary helpers
  3. Finals Club (c) 2014
  4. Author: Bryan Bonvallet
  5. """
  6. # I'd wager none of these are thread safe.
  7. import re
  8. from collections import MutableMapping
  9. needs_mapping = re.compile('{[a-zA-Z_]\w*}')
  10. class attrdict(object):
  11. """ Access dictionary by object attributes. """
  12. def __getattr__(self, attr):
  13. # create call stack, if not already there, to avoid loops
  14. try:
  15. callstack = object.__getattribute__(self, '__callstack')
  16. except AttributeError:
  17. object.__setattr__(self, '__callstack', [])
  18. callstack = object.__getattribute__(self, '__callstack')
  19. try:
  20. # don't call something already on the stack
  21. if attr in callstack:
  22. raise KeyError()
  23. # track this attribute before possibly recursing
  24. callstack.append(attr)
  25. retattr = self.__getitem__(attr)
  26. # success, remove attr from stack
  27. callstack.remove(attr)
  28. return retattr
  29. except KeyError:
  30. # if code is here, attr is not in dict
  31. try:
  32. # try to grab attr from the object
  33. retattr = super(attrdict, self).__getattribute__(attr)
  34. return retattr
  35. finally:
  36. # remove stack now that the attribute succeeded or failed
  37. callstack.remove(attr)
  38. def __setattr__(self, attr, value):
  39. self.__setitem__(attr, value)
  40. def __delattr__(self, attr):
  41. self.__delitem__(attr)
  42. class fallbackdict(MutableMapping, attrdict):
  43. """
  44. Retrieve default values from a fallback dictionary and dynamically .format
  45. """
  46. def __init__(self, fallback, *args, **kwargs):
  47. """
  48. Supply dictionary to fall back to when keys are missing.
  49. Other arguments are handled as normal dictionary.
  50. """
  51. # dodge attrdict problems by using object.__setattr__
  52. classname = type(self).__name__
  53. object.__setattr__(self, '_{0}__internaldict'.format(classname), {})
  54. object.__setattr__(self, '_{0}__fallback'.format(classname), fallback)
  55. object.__setattr__(self, '_{0}__fetching'.format(classname), [])
  56. super(fallbackdict, self).__init__(*args, **kwargs)
  57. def __needs_format__(self, value):
  58. """ Helper to determine when a string needs formatting. """
  59. return hasattr(value, 'format') and needs_mapping.search(value)
  60. def __getitem__(self, key):
  61. """
  62. Use this dict's value if it has one otherwise grab value from parent
  63. and populate any strings with format keyword arguments.
  64. """
  65. indict = self.__internaldict
  66. fetching = self.__fetching
  67. local = (key in indict)
  68. # this will throw a key error if the key isn't in either place
  69. # which is desirable
  70. value = indict[key] if local else self.__fallback[key]
  71. if (key in fetching) or (not self.__needs_format__(value)):
  72. # already seeking this key in a recursed call
  73. # or it doesn't need any formatting
  74. # so return as-is
  75. return value
  76. # if the code is this far, strings needs formatting
  77. # **self will call this function recursively.
  78. # prevent infinite recursion from e.g. d['recurse'] = '{recurse}'
  79. fetching.append(key)
  80. value = value.format(**self)
  81. # undo infinite recursion prevention
  82. fetching.remove(key)
  83. return value
  84. def __setitem__(self, key, value):
  85. """
  86. Set the internal dict with key/value.
  87. """
  88. self.__internaldict[key] = value
  89. def __delitem__(self, key):
  90. """ Delete the key from the internal dict. """
  91. del self.__internaldict[key]
  92. def __uniquekeys__(self):
  93. """ Returns unique keys between this dictionary and the fallback. """
  94. mykeys = set(self.__internaldict.keys())
  95. fbkeys = set(self.__fallback.keys())
  96. # set union: all keys from both, no repeats.
  97. return (mykeys | fbkeys)
  98. def __iter__(self):
  99. """ Returns the keys in this dictionary and the fallback. """
  100. return iter(self.__uniquekeys__())
  101. def __len__(self):
  102. """
  103. Returns the number of keys in this dictionary and the fallback.
  104. """
  105. return len(self.__uniquekeys__())