You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 lines
5.9 KiB

4 years ago
  1. from __future__ import unicode_literals
  2. from __future__ import absolute_import
  3. from . import util
  4. from copy import deepcopy
  5. class OrderedDict(dict):
  6. """
  7. A dictionary that keeps its keys in the order in which they're inserted.
  8. Copied from Django's SortedDict with some modifications.
  9. """
  10. def __new__(cls, *args, **kwargs):
  11. instance = super(OrderedDict, cls).__new__(cls, *args, **kwargs)
  12. instance.keyOrder = []
  13. return instance
  14. def __init__(self, data=None):
  15. if data is None or isinstance(data, dict):
  16. data = data or []
  17. super(OrderedDict, self).__init__(data)
  18. self.keyOrder = list(data) if data else []
  19. else:
  20. super(OrderedDict, self).__init__()
  21. super_set = super(OrderedDict, self).__setitem__
  22. for key, value in data:
  23. # Take the ordering from first key
  24. if key not in self:
  25. self.keyOrder.append(key)
  26. # But override with last value in data (dict() does this)
  27. super_set(key, value)
  28. def __deepcopy__(self, memo):
  29. return self.__class__([(key, deepcopy(value, memo))
  30. for key, value in self.items()])
  31. def __copy__(self):
  32. # The Python's default copy implementation will alter the state
  33. # of self. The reason for this seems complex but is likely related to
  34. # subclassing dict.
  35. return self.copy()
  36. def __setitem__(self, key, value):
  37. if key not in self:
  38. self.keyOrder.append(key)
  39. super(OrderedDict, self).__setitem__(key, value)
  40. def __delitem__(self, key):
  41. super(OrderedDict, self).__delitem__(key)
  42. self.keyOrder.remove(key)
  43. def __iter__(self):
  44. return iter(self.keyOrder)
  45. def __reversed__(self):
  46. return reversed(self.keyOrder)
  47. def pop(self, k, *args):
  48. result = super(OrderedDict, self).pop(k, *args)
  49. try:
  50. self.keyOrder.remove(k)
  51. except ValueError:
  52. # Key wasn't in the dictionary in the first place. No problem.
  53. pass
  54. return result
  55. def popitem(self):
  56. result = super(OrderedDict, self).popitem()
  57. self.keyOrder.remove(result[0])
  58. return result
  59. def _iteritems(self):
  60. for key in self.keyOrder:
  61. yield key, self[key]
  62. def _iterkeys(self):
  63. for key in self.keyOrder:
  64. yield key
  65. def _itervalues(self):
  66. for key in self.keyOrder:
  67. yield self[key]
  68. if util.PY3: # pragma: no cover
  69. items = _iteritems
  70. keys = _iterkeys
  71. values = _itervalues
  72. else: # pragma: no cover
  73. iteritems = _iteritems
  74. iterkeys = _iterkeys
  75. itervalues = _itervalues
  76. def items(self):
  77. return [(k, self[k]) for k in self.keyOrder]
  78. def keys(self):
  79. return self.keyOrder[:]
  80. def values(self):
  81. return [self[k] for k in self.keyOrder]
  82. def update(self, dict_):
  83. for k in dict_:
  84. self[k] = dict_[k]
  85. def setdefault(self, key, default):
  86. if key not in self:
  87. self.keyOrder.append(key)
  88. return super(OrderedDict, self).setdefault(key, default)
  89. def value_for_index(self, index):
  90. """Returns the value of the item at the given zero-based index."""
  91. return self[self.keyOrder[index]]
  92. def insert(self, index, key, value):
  93. """Inserts the key, value pair before the item with the given index."""
  94. if key in self.keyOrder:
  95. n = self.keyOrder.index(key)
  96. del self.keyOrder[n]
  97. if n < index:
  98. index -= 1
  99. self.keyOrder.insert(index, key)
  100. super(OrderedDict, self).__setitem__(key, value)
  101. def copy(self):
  102. """Returns a copy of this object."""
  103. # This way of initializing the copy means it works for subclasses, too.
  104. return self.__class__(self)
  105. def __repr__(self):
  106. """
  107. Replaces the normal dict.__repr__ with a version that returns the keys
  108. in their Ordered order.
  109. """
  110. return '{%s}' % ', '.join(
  111. ['%r: %r' % (k, v) for k, v in self._iteritems()]
  112. )
  113. def clear(self):
  114. super(OrderedDict, self).clear()
  115. self.keyOrder = []
  116. def index(self, key):
  117. """ Return the index of a given key. """
  118. try:
  119. return self.keyOrder.index(key)
  120. except ValueError:
  121. raise ValueError("Element '%s' was not found in OrderedDict" % key)
  122. def index_for_location(self, location):
  123. """ Return index or None for a given location. """
  124. if location == '_begin':
  125. i = 0
  126. elif location == '_end':
  127. i = None
  128. elif location.startswith('<') or location.startswith('>'):
  129. i = self.index(location[1:])
  130. if location.startswith('>'):
  131. if i >= len(self):
  132. # last item
  133. i = None
  134. else:
  135. i += 1
  136. else:
  137. raise ValueError('Not a valid location: "%s". Location key '
  138. 'must start with a ">" or "<".' % location)
  139. return i
  140. def add(self, key, value, location):
  141. """ Insert by key location. """
  142. i = self.index_for_location(location)
  143. if i is not None:
  144. self.insert(i, key, value)
  145. else:
  146. self.__setitem__(key, value)
  147. def link(self, key, location):
  148. """ Change location of an existing item. """
  149. n = self.keyOrder.index(key)
  150. del self.keyOrder[n]
  151. try:
  152. i = self.index_for_location(location)
  153. if i is not None:
  154. self.keyOrder.insert(i, key)
  155. else:
  156. self.keyOrder.append(key)
  157. except Exception as e:
  158. # restore to prevent data loss and reraise
  159. self.keyOrder.insert(n, key)
  160. raise e