Skip to content

Commit

Permalink
Merge pull request #240 from eriknw/test_dicttoolz_types
Browse files Browse the repository at this point in the history
Test dicttoolz with multiple types of mutable mappings.
  • Loading branch information
eriknw committed May 5, 2015
2 parents 13f0c63 + cbc7802 commit fec53b8
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 90 deletions.
11 changes: 6 additions & 5 deletions toolz/dicttoolz.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def merge_with(func, *dicts, **kwargs):
result[k] = [v]
else:
result[k].append(v)
return valmap(func, result, factory=factory)
return valmap(func, result, factory)


def valmap(func, d, factory=dict):
Expand Down Expand Up @@ -249,11 +249,12 @@ def update_in(d, keys, func, default=None, factory=dict):
assert len(keys) > 0
k, ks = keys[0], keys[1:]
if ks:
return assoc(d, k, update_in(d.get(k, factory()), ks, func, default),
factory=factory)
return assoc(d, k, update_in(d[k] if (k in d) else factory(),
ks, func, default, factory),
factory)
else:
innermost = func(d.get(k)) if (k in d) else func(default)
return assoc(d, k, innermost, factory=factory)
innermost = func(d[k]) if (k in d) else func(default)
return assoc(d, k, innermost, factory)


def get_in(keys, coll, default=None, no_default=False):
Expand Down
277 changes: 192 additions & 85 deletions toolz/tests/test_dicttoolz.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@
assoc, dissoc, keyfilter, valfilter, itemmap,
itemfilter)
from toolz.utils import raises


class defaultdict(_defaultdict):
def __eq__(self, other):
return (super(defaultdict, self).__eq__(other) and
isinstance(other, _defaultdict) and
self.default_factory == other.default_factory)
from toolz.compatibility import PY3


def inc(x):
Expand All @@ -20,111 +14,224 @@ def iseven(i):
return i % 2 == 0


def test_merge():
assert merge({1: 1, 2: 2}, {3: 4}) == {1: 1, 2: 2, 3: 4}
class TestDict(object):
"""Test typical usage: dict inputs, no factory keyword.
Class attributes:
D: callable that inputs a dict and creates or returns a MutableMapping
kw: kwargs dict to specify "factory" keyword (if applicable)
"""
D = dict
kw = {}

def test_merge(self):
D, kw = self.D, self.kw
assert merge(D({1: 1, 2: 2}), D({3: 4}), **kw) == D({1: 1, 2: 2, 3: 4})

def test_merge_iterable_arg(self):
D, kw = self.D, self.kw
assert merge([D({1: 1, 2: 2}), D({3: 4})], **kw) == D({1: 1, 2: 2, 3: 4})

def test_merge_with(self):
D, kw = self.D, self.kw
dicts = D({1: 1, 2: 2}), D({1: 10, 2: 20})
assert merge_with(sum, *dicts, **kw) == D({1: 11, 2: 22})
assert merge_with(tuple, *dicts, **kw) == D({1: (1, 10), 2: (2, 20)})

dicts = D({1: 1, 2: 2, 3: 3}), D({1: 10, 2: 20})
assert merge_with(sum, *dicts, **kw) == D({1: 11, 2: 22, 3: 3})
assert merge_with(tuple, *dicts, **kw) == D({1: (1, 10), 2: (2, 20), 3: (3,)})

assert not merge_with(sum)

def test_merge_with_iterable_arg(self):
D, kw = self.D, self.kw
dicts = D({1: 1, 2: 2}), D({1: 10, 2: 20})
assert merge_with(sum, *dicts, **kw) == D({1: 11, 2: 22})
assert merge_with(sum, dicts, **kw) == D({1: 11, 2: 22})
assert merge_with(sum, iter(dicts), **kw) == D({1: 11, 2: 22})

def test_valmap(self):
D, kw = self.D, self.kw
assert valmap(inc, D({1: 1, 2: 2}), **kw) == D({1: 2, 2: 3})

def test_keymap(self):
D, kw = self.D, self.kw
assert keymap(inc, D({1: 1, 2: 2}), **kw) == D({2: 1, 3: 2})

def test_itemmap(self):
D, kw = self.D, self.kw
assert itemmap(reversed, D({1: 2, 2: 4}), **kw) == D({2: 1, 4: 2})

def test_valfilter(self):
D, kw = self.D, self.kw
assert valfilter(iseven, D({1: 2, 2: 3}), **kw) == D({1: 2})

def test_keyfilter(self):
D, kw = self.D, self.kw
assert keyfilter(iseven, D({1: 2, 2: 3}), **kw) == D({2: 3})

def test_itemfilter(self):
D, kw = self.D, self.kw
assert itemfilter(lambda item: iseven(item[0]), D({1: 2, 2: 3}), **kw) == D({2: 3})
assert itemfilter(lambda item: iseven(item[1]), D({1: 2, 2: 3}), **kw) == D({1: 2})

def test_assoc(self):
D, kw = self.D, self.kw
assert assoc(D({}), "a", 1, **kw) == D({"a": 1})
assert assoc(D({"a": 1}), "a", 3, **kw) == D({"a": 3})
assert assoc(D({"a": 1}), "b", 3, **kw) == D({"a": 1, "b": 3})

# Verify immutability:
d = D({'x': 1})
oldd = d
assoc(d, 'x', 2, **kw)
assert d is oldd

def test_dissoc(self):
D, kw = self.D, self.kw
assert dissoc(D({"a": 1}), "a") == D({})
assert dissoc(D({"a": 1, "b": 2}), "a") == D({"b": 2})
assert dissoc(D({"a": 1, "b": 2}), "b") == D({"a": 1})

# Verify immutability:
d = D({'x': 1})
oldd = d
d2 = dissoc(d, 'x')
assert d is oldd
assert d2 is not oldd

def test_update_in(self):
D, kw = self.D, self.kw
assert update_in(D({"a": 0}), ["a"], inc, **kw) == D({"a": 1})
assert update_in(D({"a": 0, "b": 1}), ["b"], str, **kw) == D({"a": 0, "b": "1"})
assert (update_in(D({"t": 1, "v": D({"a": 0})}), ["v", "a"], inc, **kw) ==
D({"t": 1, "v": D({"a": 1})}))
# Handle one missing key.
assert update_in(D({}), ["z"], str, None, **kw) == D({"z": "None"})
assert update_in(D({}), ["z"], inc, 0, **kw) == D({"z": 1})
assert update_in(D({}), ["z"], lambda x: x+"ar", default="b", **kw) == D({"z": "bar"})
# Same semantics as Clojure for multiple missing keys, ie. recursively
# create nested empty dictionaries to the depth specified by the
# keys with the innermost value set to f(default).
assert update_in(D({}), [0, 1], inc, default=-1, **kw) == D({0: D({1: 0})})
assert update_in(D({}), [0, 1], str, default=100, **kw) == D({0: D({1: "100"})})
assert (update_in(D({"foo": "bar", 1: 50}), ["d", 1, 0], str, 20, **kw) ==
D({"foo": "bar", 1: 50, "d": D({1: D({0: "20"})})}))
# Verify immutability:
d = D({'x': 1})
oldd = d
update_in(d, ['x'], inc, **kw)
assert d is oldd

def test_factory(self):
D, kw = self.D, self.kw
assert merge(defaultdict(int, D({1: 2})), D({2: 3})) == {1: 2, 2: 3}
assert (merge(defaultdict(int, D({1: 2})), D({2: 3}),
factory=lambda: defaultdict(int)) ==
defaultdict(int, D({1: 2, 2: 3})))
assert not (merge(defaultdict(int, D({1: 2})), D({2: 3}),
factory=lambda: defaultdict(int)) == {1: 2, 2: 3})
assert raises(TypeError, lambda: merge(D({1: 2}), D({2: 3}), factoryy=dict))


def test_merge_iterable_arg():
assert merge([{1: 1, 2: 2}, {3: 4}]) == {1: 1, 2: 2, 3: 4}
class defaultdict(_defaultdict):
def __eq__(self, other):
return (super(defaultdict, self).__eq__(other) and
isinstance(other, _defaultdict) and
self.default_factory == other.default_factory)


def test_merge_with():
dicts = {1: 1, 2: 2}, {1: 10, 2: 20}
assert merge_with(sum, *dicts) == {1: 11, 2: 22}
assert merge_with(tuple, *dicts) == {1: (1, 10), 2: (2, 20)}
class TestDefaultDict(TestDict):
"""Test defaultdict as input and factory
dicts = {1: 1, 2: 2, 3: 3}, {1: 10, 2: 20}
assert merge_with(sum, *dicts) == {1: 11, 2: 22, 3: 3}
assert merge_with(tuple, *dicts) == {1: (1, 10), 2: (2, 20), 3: (3,)}
Class attributes:
D: callable that inputs a dict and creates or returns a MutableMapping
kw: kwargs dict to specify "factory" keyword (if applicable)
"""
@staticmethod
def D(dict_):
return defaultdict(int, dict_)

assert not merge_with(sum)
kw = {'factory': lambda: defaultdict(int)}


def test_merge_with_iterable_arg():
dicts = {1: 1, 2: 2}, {1: 10, 2: 20}
assert merge_with(sum, *dicts) == {1: 11, 2: 22}
assert merge_with(sum, dicts) == {1: 11, 2: 22}
assert merge_with(sum, iter(dicts)) == {1: 11, 2: 22}
class CustomMapping(object):
"""Define methods of the MutableMapping protocol required by dicttoolz"""
def __init__(self, *args, **kwargs):
self._d = dict(*args, **kwargs)

def __getitem__(self, key):
return self._d[key]

def test_valmap():
assert valmap(inc, {1: 1, 2: 2}) == {1: 2, 2: 3}
def __setitem__(self, key, val):
self._d[key] = val

def __delitem__(self, key):
del self._d[key]

def test_keymap():
assert keymap(inc, {1: 1, 2: 2}) == {2: 1, 3: 2}
def __iter__(self):
return iter(self._d)

def __len__(self):
return len(self._d)

def __contains__(self, key):
return key in self._d

def __eq__(self, other):
return isinstance(other, CustomMapping) and self._d == other._d

def test_itemmap():
assert itemmap(reversed, {1: 2, 2: 4}) == {2: 1, 4: 2}
def __ne__(self, other):
return not isinstance(other, CustomMapping) or self._d != other._d

def keys(self):
return self._d.keys()

def test_valfilter():
assert valfilter(iseven, {1: 2, 2: 3}) == {1: 2}
def values(self):
return self._d.values()

def items(self):
return self._d.items()

def test_keyfilter():
assert keyfilter(iseven, {1: 2, 2: 3}) == {2: 3}
def update(self, *args, **kwargs):
self._d.update(*args, **kwargs)

# Should we require these to be defined for Python 2?
if not PY3:
def iterkeys(self):
return self._d.iterkeys()

def test_itemfilter():
assert itemfilter(lambda item: iseven(item[0]), {1: 2, 2: 3}) == {2: 3}
assert itemfilter(lambda item: iseven(item[1]), {1: 2, 2: 3}) == {1: 2}
def itervalues(self):
return self._d.itervalues()

def iteritems(self):
return self._d.iteritems()

def test_assoc():
assert assoc({}, "a", 1) == {"a": 1}
assert assoc({"a": 1}, "a", 3) == {"a": 3}
assert assoc({"a": 1}, "b", 3) == {"a": 1, "b": 3}
# Unused methods that are part of the MutableMapping protocol
#def get(self, key, *args):
# return self._d.get(key, *args)

# Verify immutability:
d = {'x': 1}
oldd = d
assoc(d, 'x', 2)
assert d is oldd
#def pop(self, key, *args):
# return self._d.pop(key, *args)

#def popitem(self, key):
# return self._d.popitem()

def test_dissoc():
assert dissoc({"a": 1}, "a") == {}
assert dissoc({"a": 1, "b": 2}, "a") == {"b": 2}
assert dissoc({"a": 1, "b": 2}, "b") == {"a": 1}
#def clear(self):
# self._d.clear()

# Verify immutability:
d = {'x': 1}
oldd = d
d2 = dissoc(d, 'x')
assert d is oldd
assert d2 is not oldd
#def setdefault(self, key, *args):
# return self._d.setdefault(self, key, *args)


def test_update_in():
assert update_in({"a": 0}, ["a"], inc) == {"a": 1}
assert update_in({"a": 0, "b": 1}, ["b"], str) == {"a": 0, "b": "1"}
assert (update_in({"t": 1, "v": {"a": 0}}, ["v", "a"], inc) ==
{"t": 1, "v": {"a": 1}})
# Handle one missing key.
assert update_in({}, ["z"], str, None) == {"z": "None"}
assert update_in({}, ["z"], inc, 0) == {"z": 1}
assert update_in({}, ["z"], lambda x: x+"ar", default="b") == {"z": "bar"}
# Same semantics as Clojure for multiple missing keys, ie. recursively
# create nested empty dictionaries to the depth specified by the
# keys with the innermost value set to f(default).
assert update_in({}, [0, 1], inc, default=-1) == {0: {1: 0}}
assert update_in({}, [0, 1], str, default=100) == {0: {1: "100"}}
assert (update_in({"foo": "bar", 1: 50}, ["d", 1, 0], str, 20) ==
{"foo": "bar", 1: 50, "d": {1: {0: "20"}}})
# Verify immutability:
d = {'x': 1}
oldd = d
update_in(d, ['x'], inc)
assert d is oldd
class TestCustomMapping(TestDict):
"""Test CustomMapping as input and factory
Class attributes:
D: callable that inputs a dict and creates or returns a MutableMapping
kw: kwargs dict to specify "factory" keyword (if applicable)
"""
D = CustomMapping
kw = {'factory': lambda: CustomMapping()}

def test_factory():
assert merge(defaultdict(int, {1: 2}), {2: 3}) == {1: 2, 2: 3}
assert (merge(defaultdict(int, {1: 2}), {2: 3},
factory=lambda: defaultdict(int)) ==
defaultdict(int, {1: 2, 2: 3}))
assert not (merge(defaultdict(int, {1: 2}), {2: 3},
factory=lambda: defaultdict(int)) == {1: 2, 2: 3})
assert raises(TypeError, lambda: merge({1: 2}, {2: 3}, factoryy=dict))

0 comments on commit fec53b8

Please sign in to comment.