123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- # -*- coding: utf-8 -*-
- import sys
- import pytest
- from boltons.dictutils import OMD, OneToOne, ManyToMany, FrozenDict, subdict, FrozenHashError
- _ITEMSETS = [[],
- [('a', 1), ('b', 2), ('c', 3)],
- [('A', 'One'), ('A', 'One'), ('A', 'One')],
- [('Z', -1), ('Y', -2), ('Y', -2)],
- [('a', 1), ('b', 2), ('a', 3), ('c', 4)]]
- def test_dict_init():
- d = dict(_ITEMSETS[1])
- omd = OMD(d)
- assert omd['a'] == 1
- assert omd['b'] == 2
- assert omd['c'] == 3
- assert len(omd) == 3
- assert omd.getlist('a') == [1]
- assert omd == d
- def test_todict():
- omd = OMD(_ITEMSETS[2])
- assert len(omd) == 1
- assert omd['A'] == 'One'
- d = omd.todict(multi=True)
- assert len(d) == 1
- assert d['A'] == ['One', 'One', 'One']
- flat = omd.todict()
- assert flat['A'] == 'One'
- for itemset in _ITEMSETS:
- omd = OMD(itemset)
- d = dict(itemset)
- flat = omd.todict()
- assert flat == d
- return
- def test_eq():
- omd = OMD(_ITEMSETS[3])
- assert omd == omd
- assert not (omd != omd)
- omd2 = OMD(_ITEMSETS[3])
- assert omd == omd2
- assert omd2 == omd
- assert not (omd != omd2)
- d = dict(_ITEMSETS[3])
- assert d == omd
- omd3 = OMD(d)
- assert omd != omd3
- def test_copy():
- for itemset in _ITEMSETS:
- omd = OMD(itemset)
- omd_c = omd.copy()
- assert omd == omd_c
- if omd_c:
- omd_c.pop(itemset[0][0])
- assert omd != omd_c
- return
- def test_clear():
- for itemset in _ITEMSETS:
- omd = OMD(itemset)
- omd.clear()
- assert len(omd) == 0
- assert not omd
- omd.clear()
- assert not omd
- omd['a'] = 22
- assert omd
- omd.clear()
- assert not omd
- def test_types():
- try:
- from collections.abc import MutableMapping
- except ImportError:
- from collections import MutableMapping
- omd = OMD()
- assert isinstance(omd, dict)
- assert isinstance(omd, MutableMapping)
- def test_multi_correctness():
- size = 100
- redun = 5
- _rng = range(size)
- _rng_redun = list(range(size//redun)) * redun
- _pairs = zip(_rng_redun, _rng)
- omd = OMD(_pairs)
- for multi in (True, False):
- vals = [x[1] for x in omd.iteritems(multi=multi)]
- strictly_ascending = all([x < y for x, y in zip(vals, vals[1:])])
- assert strictly_ascending
- return
- def test_kv_consistency():
- for itemset in _ITEMSETS:
- omd = OMD(itemset)
- for multi in (True, False):
- items = omd.items(multi=multi)
- keys = omd.keys(multi=multi)
- values = omd.values(multi=multi)
- assert keys == [x[0] for x in items]
- assert values == [x[1] for x in items]
- return
- def test_update_basic():
- omd = OMD(_ITEMSETS[1])
- omd2 = OMD({'a': 10})
- omd.update(omd2)
- assert omd['a'] == 10
- assert omd.getlist('a') == [10]
- omd2_c = omd2.copy()
- omd2_c.pop('a')
- assert omd2 != omd2_c
- def test_update():
- for first, second in zip(_ITEMSETS, _ITEMSETS[1:]):
- omd1 = OMD(first)
- omd2 = OMD(second)
- ref1 = dict(first)
- ref2 = dict(second)
- omd1.update(omd2)
- ref1.update(ref2)
- assert omd1.todict() == ref1
- omd1_repr = repr(omd1)
- omd1.update(omd1)
- assert omd1_repr == repr(omd1)
- def test_update_extend():
- for first, second in zip(_ITEMSETS, _ITEMSETS[1:] + [[]]):
- omd1 = OMD(first)
- omd2 = OMD(second)
- ref = dict(first)
- orig_keys = set(omd1)
- ref.update(second)
- omd1.update_extend(omd2)
- for k in omd2:
- assert len(omd1.getlist(k)) >= len(omd2.getlist(k))
- assert omd1.todict() == ref
- assert orig_keys <= set(omd1)
- def test_invert():
- for items in _ITEMSETS:
- omd = OMD(items)
- iomd = omd.inverted()
- # first, test all items made the jump
- assert len(omd.items(multi=True)) == len(iomd.items(multi=True))
- for val in omd.values():
- assert val in iomd # all values present as keys
- def test_poplast():
- for items in _ITEMSETS[1:]:
- omd = OMD(items)
- assert omd.poplast() == items[-1][-1]
- def test_pop():
- omd = OMD()
- omd.add('even', 0)
- omd.add('odd', 1)
- omd.add('even', 2)
- assert omd.pop('odd') == 1
- assert omd.pop('odd', 99) == 99
- try:
- omd.pop('odd')
- assert False
- except KeyError:
- pass
- assert len(omd) == 1
- assert len(omd.items(multi=True)) == 2
- def test_addlist():
- omd = OMD()
- omd.addlist('a', [1, 2, 3])
- omd.addlist('b', [4, 5])
- assert omd.keys() == ['a', 'b']
- assert len(list(omd.iteritems(multi=True))) == 5
- e_omd = OMD()
- e_omd.addlist('a', [])
- assert e_omd.keys() == []
- assert len(list(e_omd.iteritems(multi=True))) == 0
- def test_pop_all():
- omd = OMD()
- omd.add('even', 0)
- omd.add('odd', 1)
- omd.add('even', 2)
- assert omd.popall('odd') == [1]
- assert len(omd) == 1
- try:
- omd.popall('odd')
- assert False
- except KeyError:
- pass
- assert omd.popall('odd', None) is None
- assert omd.popall('even') == [0, 2]
- assert len(omd) == 0
- assert omd.popall('nope', None) is None
- assert OMD().popall('', None) is None
- def test_reversed():
- try:
- from collections import OrderedDict
- except:
- # skip on python 2.6
- return
- for items in _ITEMSETS:
- omd = OMD(items)
- od = OrderedDict(items)
- for ik, ok in zip(reversed(od), reversed(omd)):
- assert ik == ok
- r100 = range(100)
- omd = OMD(zip(r100, r100))
- for i in r100:
- omd.add(i, i)
- r100 = list(reversed(r100))
- assert list(reversed(omd)) == r100
- omd = OMD()
- assert list(reversed(omd)) == list(reversed(omd.keys()))
- for i in range(20):
- for j in range(i):
- omd.add(i, i)
- assert list(reversed(omd)) == list(reversed(omd.keys()))
- def test_setdefault():
- omd = OMD()
- empty_list = []
- x = omd.setdefault('1', empty_list)
- assert x is empty_list
- y = omd.setdefault('2')
- assert y is None
- assert omd.setdefault('1', None) is empty_list
- e_omd = OMD()
- e_omd.addlist(1, [])
- assert e_omd.popall(1, None) is None
- assert len(e_omd) == 0
- ## END OMD TESTS
- import string
- def test_subdict():
- cap_map = dict([(x, x.upper()) for x in string.hexdigits])
- assert len(cap_map) == 22
- assert len(subdict(cap_map, drop=['a'])) == 21
- assert 'a' not in subdict(cap_map, drop=['a'])
- assert len(subdict(cap_map, keep=['a', 'b'])) == 2
- def test_subdict_keep_type():
- omd = OMD({'a': 'A'})
- assert subdict(omd) == omd
- assert type(subdict(omd)) is OMD
- def test_one_to_one():
- e = OneToOne({1:2})
- def ck(val, inv):
- assert (e, e.inv) == (val, inv)
- ck({1:2}, {2:1})
- e[2] = 3
- ck({1:2, 2:3}, {3:2, 2:1})
- e.clear()
- ck({}, {})
- e[1] = 1
- ck({1:1}, {1:1})
- e[1] = 2
- ck({1:2}, {2:1})
- e[3] = 2
- ck({3:2}, {2:3})
- del e[3]
- ck({}, {})
- e[1] = 2
- e.inv[2] = 3
- ck({3:2}, {2:3})
- del e.inv[2]
- ck({}, {})
- assert OneToOne({1:2, 3:4}).copy().inv == {2:1, 4:3}
- e[1] = 2
- e.pop(1)
- ck({}, {})
- e[1] = 2
- e.inv.pop(2)
- ck({}, {})
- e[1] = 2
- e.popitem()
- ck({}, {})
- e.setdefault(1)
- ck({1: None}, {None: 1})
- e.inv.setdefault(2)
- ck({1: None, None: 2}, {None: 1, 2: None})
- e.clear()
- e.update({1:2}, cat="dog")
- ck({1:2, "cat":"dog"}, {2:1, "dog":"cat"})
- # try various overlapping values
- oto = OneToOne({'a': 0, 'b': 0})
- assert len(oto) == len(oto.inv) == 1
- oto['c'] = 0
- assert len(oto) == len(oto.inv) == 1
- assert oto.inv[0] == 'c'
- oto.update({'z': 0, 'y': 0})
- assert len(oto) == len(oto.inv) == 1
- # test out unique classmethod
- with pytest.raises(ValueError):
- OneToOne.unique({'a': 0, 'b': 0})
- return
- def test_many_to_many():
- m2m = ManyToMany()
- assert len(m2m) == 0
- assert not m2m
- m2m.add(1, 'a')
- assert m2m
- m2m.add(1, 'b')
- assert len(m2m) == 1
- assert m2m[1] == frozenset(['a', 'b'])
- assert m2m.inv['a'] == frozenset([1])
- del m2m.inv['a']
- assert m2m[1] == frozenset(['b'])
- assert 1 in m2m
- del m2m.inv['b']
- assert 1 not in m2m
- m2m[1] = ('a', 'b')
- assert set(m2m.iteritems()) == set([(1, 'a'), (1, 'b')])
- m2m.remove(1, 'a')
- m2m.remove(1, 'b')
- assert 1 not in m2m
- m2m.update([(1, 'a'), (2, 'b')])
- assert m2m.get(2) == frozenset(('b',))
- assert m2m.get(3) == frozenset(())
- assert ManyToMany(['ab', 'cd']) == ManyToMany(['ba', 'dc']).inv
- assert ManyToMany(ManyToMany(['ab', 'cd'])) == ManyToMany(['ab', 'cd'])
- m2m = ManyToMany({'a': 'b'})
- m2m.replace('a', 'B')
- # also test the repr while we're at it
- assert repr(m2m) == repr(ManyToMany([("B", "b")]))
- assert repr(m2m).startswith('ManyToMany(') and 'B' in repr(m2m)
- def test_frozendict():
- efd = FrozenDict()
- assert isinstance(efd, dict)
- assert len(efd) == 0
- assert not efd
- assert repr(efd) == "FrozenDict({})"
- data = {'a': 'A', 'b': 'B'}
- fd = FrozenDict(data)
- assert bool(fd)
- assert len(fd) == 2
- assert fd['a'] == 'A'
- assert fd['b'] == 'B'
- assert sorted(fd.keys()) == ['a', 'b']
- assert sorted(fd.values()) == ['A', 'B']
- assert sorted(fd.items()) == [('a', 'A'), ('b', 'B')]
- assert 'a' in fd
- assert 'c' not in fd
- assert hash(fd)
- fd_map = {'fd': fd}
- assert fd_map['fd'] is fd
- with pytest.raises(TypeError):
- fd['c'] = 'C'
- with pytest.raises(TypeError):
- del fd['a']
- with pytest.raises(TypeError):
- fd.update(x='X')
- with pytest.raises(TypeError):
- fd.setdefault('x', [])
- with pytest.raises(TypeError):
- fd.pop('c')
- with pytest.raises(TypeError):
- fd.popitem()
- with pytest.raises(TypeError):
- fd.clear()
- import pickle
- fkfd = FrozenDict.fromkeys([2, 4, 6], value=0)
- assert pickle.loads(pickle.dumps(fkfd)) == fkfd
- assert sorted(fkfd.updated({8: 0}).keys()) == [2, 4, 6, 8]
- # try something with an unhashable value
- unfd = FrozenDict({'a': ['A']})
- with pytest.raises(TypeError) as excinfo:
- {unfd: 'val'}
- assert excinfo.type is FrozenHashError
- with pytest.raises(TypeError) as excinfo2:
- {unfd: 'val'}
- assert excinfo.value is excinfo2.value # test cached exception
- return
- @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9 or higher")
- def test_frozendict_ior():
- data = {'a': 'A', 'b': 'B'}
- fd = FrozenDict(data)
- with pytest.raises(TypeError, match=".*FrozenDict.*immutable.*"):
- fd |= fd
- def test_frozendict_api():
- # all the read-only methods that are fine
- through_methods = ['__class__',
- '__cmp__',
- '__contains__',
- '__delattr__',
- '__dir__',
- '__eq__',
- '__format__',
- '__ge__',
- '__getattribute__',
- '__getstate__',
- '__getitem__',
- '__getstate__',
- '__gt__',
- '__init__',
- '__iter__',
- '__le__',
- '__len__',
- '__lt__',
- '__ne__',
- '__new__',
- '__or__',
- '__reduce__',
- '__reversed__',
- '__ror__',
- '__setattr__',
- '__sizeof__',
- '__str__',
- 'copy',
- 'get',
- 'has_key',
- 'items',
- 'iteritems',
- 'iterkeys',
- 'itervalues',
- 'keys',
- 'values',
- 'viewitems',
- 'viewkeys',
- 'viewvalues']
- fd = FrozenDict()
- ret = []
- for attrname in dir(fd):
- if attrname == '_hash': # in the dir, even before it's set
- continue
- attr = getattr(fd, attrname)
- if not callable(attr):
- continue
- if getattr(FrozenDict, attrname) == getattr(dict, attrname, None) and attrname not in through_methods:
- assert attrname == False
- ret.append(attrname)
- import copy
- assert copy.copy(fd) is fd
|