test_dicttoolz.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. from collections import defaultdict as _defaultdict
  2. import os
  3. from cytoolz.dicttoolz import (merge, merge_with, valmap, keymap, update_in,
  4. assoc, dissoc, keyfilter, valfilter, itemmap,
  5. itemfilter, assoc_in)
  6. from cytoolz.functoolz import identity
  7. from cytoolz.utils import raises
  8. from cytoolz.compatibility import PY3
  9. def inc(x):
  10. return x + 1
  11. def iseven(i):
  12. return i % 2 == 0
  13. class TestDict(object):
  14. """Test typical usage: dict inputs, no factory keyword.
  15. Class attributes:
  16. D: callable that inputs a dict and creates or returns a MutableMapping
  17. kw: kwargs dict to specify "factory" keyword (if applicable)
  18. """
  19. D = dict
  20. kw = {}
  21. def test_merge(self):
  22. D, kw = self.D, self.kw
  23. assert merge(D({1: 1, 2: 2}), D({3: 4}), **kw) == D({1: 1, 2: 2, 3: 4})
  24. def test_merge_iterable_arg(self):
  25. D, kw = self.D, self.kw
  26. assert merge([D({1: 1, 2: 2}), D({3: 4})], **kw) == D({1: 1, 2: 2, 3: 4})
  27. def test_merge_with(self):
  28. D, kw = self.D, self.kw
  29. dicts = D({1: 1, 2: 2}), D({1: 10, 2: 20})
  30. assert merge_with(sum, *dicts, **kw) == D({1: 11, 2: 22})
  31. assert merge_with(tuple, *dicts, **kw) == D({1: (1, 10), 2: (2, 20)})
  32. dicts = D({1: 1, 2: 2, 3: 3}), D({1: 10, 2: 20})
  33. assert merge_with(sum, *dicts, **kw) == D({1: 11, 2: 22, 3: 3})
  34. assert merge_with(tuple, *dicts, **kw) == D({1: (1, 10), 2: (2, 20), 3: (3,)})
  35. assert not merge_with(sum)
  36. def test_merge_with_iterable_arg(self):
  37. D, kw = self.D, self.kw
  38. dicts = D({1: 1, 2: 2}), D({1: 10, 2: 20})
  39. assert merge_with(sum, *dicts, **kw) == D({1: 11, 2: 22})
  40. assert merge_with(sum, dicts, **kw) == D({1: 11, 2: 22})
  41. assert merge_with(sum, iter(dicts), **kw) == D({1: 11, 2: 22})
  42. def test_valmap(self):
  43. D, kw = self.D, self.kw
  44. assert valmap(inc, D({1: 1, 2: 2}), **kw) == D({1: 2, 2: 3})
  45. def test_keymap(self):
  46. D, kw = self.D, self.kw
  47. assert keymap(inc, D({1: 1, 2: 2}), **kw) == D({2: 1, 3: 2})
  48. def test_itemmap(self):
  49. D, kw = self.D, self.kw
  50. assert itemmap(reversed, D({1: 2, 2: 4}), **kw) == D({2: 1, 4: 2})
  51. def test_valfilter(self):
  52. D, kw = self.D, self.kw
  53. assert valfilter(iseven, D({1: 2, 2: 3}), **kw) == D({1: 2})
  54. def test_keyfilter(self):
  55. D, kw = self.D, self.kw
  56. assert keyfilter(iseven, D({1: 2, 2: 3}), **kw) == D({2: 3})
  57. def test_itemfilter(self):
  58. D, kw = self.D, self.kw
  59. assert itemfilter(lambda item: iseven(item[0]), D({1: 2, 2: 3}), **kw) == D({2: 3})
  60. assert itemfilter(lambda item: iseven(item[1]), D({1: 2, 2: 3}), **kw) == D({1: 2})
  61. def test_assoc(self):
  62. D, kw = self.D, self.kw
  63. assert assoc(D({}), "a", 1, **kw) == D({"a": 1})
  64. assert assoc(D({"a": 1}), "a", 3, **kw) == D({"a": 3})
  65. assert assoc(D({"a": 1}), "b", 3, **kw) == D({"a": 1, "b": 3})
  66. # Verify immutability:
  67. d = D({'x': 1})
  68. oldd = d
  69. assoc(d, 'x', 2, **kw)
  70. assert d is oldd
  71. def test_dissoc(self):
  72. D, kw = self.D, self.kw
  73. assert dissoc(D({"a": 1}), "a", **kw) == D({})
  74. assert dissoc(D({"a": 1, "b": 2}), "a", **kw) == D({"b": 2})
  75. assert dissoc(D({"a": 1, "b": 2}), "b", **kw) == D({"a": 1})
  76. assert dissoc(D({"a": 1, "b": 2}), "a", "b", **kw) == D({})
  77. assert dissoc(D({"a": 1}), "a", **kw) == dissoc(dissoc(D({"a": 1}), "a", **kw), "a", **kw)
  78. # Verify immutability:
  79. d = D({'x': 1})
  80. oldd = d
  81. d2 = dissoc(d, 'x', **kw)
  82. assert d is oldd
  83. assert d2 is not oldd
  84. def test_assoc_in(self):
  85. D, kw = self.D, self.kw
  86. assert assoc_in(D({"a": 1}), ["a"], 2, **kw) == D({"a": 2})
  87. assert (assoc_in(D({"a": D({"b": 1})}), ["a", "b"], 2, **kw) ==
  88. D({"a": D({"b": 2})}))
  89. assert assoc_in(D({}), ["a", "b"], 1, **kw) == D({"a": D({"b": 1})})
  90. # Verify immutability:
  91. d = D({'x': 1})
  92. oldd = d
  93. d2 = assoc_in(d, ['x'], 2, **kw)
  94. assert d is oldd
  95. assert d2 is not oldd
  96. def test_update_in(self):
  97. D, kw = self.D, self.kw
  98. assert update_in(D({"a": 0}), ["a"], inc, **kw) == D({"a": 1})
  99. assert update_in(D({"a": 0, "b": 1}), ["b"], str, **kw) == D({"a": 0, "b": "1"})
  100. assert (update_in(D({"t": 1, "v": D({"a": 0})}), ["v", "a"], inc, **kw) ==
  101. D({"t": 1, "v": D({"a": 1})}))
  102. # Handle one missing key.
  103. assert update_in(D({}), ["z"], str, None, **kw) == D({"z": "None"})
  104. assert update_in(D({}), ["z"], inc, 0, **kw) == D({"z": 1})
  105. assert update_in(D({}), ["z"], lambda x: x+"ar", default="b", **kw) == D({"z": "bar"})
  106. # Same semantics as Clojure for multiple missing keys, ie. recursively
  107. # create nested empty dictionaries to the depth specified by the
  108. # keys with the innermost value set to f(default).
  109. assert update_in(D({}), [0, 1], inc, default=-1, **kw) == D({0: D({1: 0})})
  110. assert update_in(D({}), [0, 1], str, default=100, **kw) == D({0: D({1: "100"})})
  111. assert (update_in(D({"foo": "bar", 1: 50}), ["d", 1, 0], str, 20, **kw) ==
  112. D({"foo": "bar", 1: 50, "d": D({1: D({0: "20"})})}))
  113. # Verify immutability:
  114. d = D({'x': 1})
  115. oldd = d
  116. update_in(d, ['x'], inc, **kw)
  117. assert d is oldd
  118. def test_factory(self):
  119. D, kw = self.D, self.kw
  120. assert merge(defaultdict(int, D({1: 2})), D({2: 3})) == {1: 2, 2: 3}
  121. assert (merge(defaultdict(int, D({1: 2})), D({2: 3}),
  122. factory=lambda: defaultdict(int)) ==
  123. defaultdict(int, D({1: 2, 2: 3})))
  124. assert not (merge(defaultdict(int, D({1: 2})), D({2: 3}),
  125. factory=lambda: defaultdict(int)) == {1: 2, 2: 3})
  126. assert raises(TypeError, lambda: merge(D({1: 2}), D({2: 3}), factoryy=dict))
  127. class defaultdict(_defaultdict):
  128. def __eq__(self, other):
  129. return (super(defaultdict, self).__eq__(other) and
  130. isinstance(other, _defaultdict) and
  131. self.default_factory == other.default_factory)
  132. class TestDefaultDict(TestDict):
  133. """Test defaultdict as input and factory
  134. Class attributes:
  135. D: callable that inputs a dict and creates or returns a MutableMapping
  136. kw: kwargs dict to specify "factory" keyword (if applicable)
  137. """
  138. @staticmethod
  139. def D(dict_):
  140. return defaultdict(int, dict_)
  141. kw = {'factory': lambda: defaultdict(int)}
  142. class CustomMapping(object):
  143. """Define methods of the MutableMapping protocol required by dicttoolz"""
  144. def __init__(self, *args, **kwargs):
  145. self._d = dict(*args, **kwargs)
  146. def __getitem__(self, key):
  147. return self._d[key]
  148. def __setitem__(self, key, val):
  149. self._d[key] = val
  150. def __delitem__(self, key):
  151. del self._d[key]
  152. def __iter__(self):
  153. return iter(self._d)
  154. def __len__(self):
  155. return len(self._d)
  156. def __contains__(self, key):
  157. return key in self._d
  158. def __eq__(self, other):
  159. return isinstance(other, CustomMapping) and self._d == other._d
  160. def __ne__(self, other):
  161. return not isinstance(other, CustomMapping) or self._d != other._d
  162. def keys(self):
  163. return self._d.keys()
  164. def values(self):
  165. return self._d.values()
  166. def items(self):
  167. return self._d.items()
  168. def update(self, *args, **kwargs):
  169. self._d.update(*args, **kwargs)
  170. # Should we require these to be defined for Python 2?
  171. if not PY3:
  172. def iterkeys(self):
  173. return self._d.iterkeys()
  174. def itervalues(self):
  175. return self._d.itervalues()
  176. def iteritems(self):
  177. return self._d.iteritems()
  178. # Unused methods that are part of the MutableMapping protocol
  179. #def get(self, key, *args):
  180. # return self._d.get(key, *args)
  181. #def pop(self, key, *args):
  182. # return self._d.pop(key, *args)
  183. #def popitem(self, key):
  184. # return self._d.popitem()
  185. #def clear(self):
  186. # self._d.clear()
  187. #def setdefault(self, key, *args):
  188. # return self._d.setdefault(self, key, *args)
  189. class TestCustomMapping(TestDict):
  190. """Test CustomMapping as input and factory
  191. Class attributes:
  192. D: callable that inputs a dict and creates or returns a MutableMapping
  193. kw: kwargs dict to specify "factory" keyword (if applicable)
  194. """
  195. D = CustomMapping
  196. kw = {'factory': lambda: CustomMapping()}
  197. def test_environ():
  198. # See: https://github.com/pycytoolz/cycytoolz/issues/127
  199. assert keymap(identity, os.environ) == os.environ
  200. assert valmap(identity, os.environ) == os.environ
  201. assert itemmap(identity, os.environ) == os.environ