File: test_collections.py

package info (click to toggle)
monty 2025.3.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 820 kB
  • sloc: python: 5,261; makefile: 136; sh: 20
file content (275 lines) | stat: -rw-r--r-- 7,093 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
from __future__ import annotations

from collections import UserDict

import pytest

from monty.collections import (
    AttrDict,
    ControlledDict,
    FrozenAttrDict,
    MongoDict,
    Namespace,
    dict2namedtuple,
    frozendict,
    tree,
)


def test_tree():
    x = tree()
    x["a"]["b"]["c"]["d"] = 1
    assert "b" in x["a"]
    assert "c" not in x["a"]
    assert "c" in x["a"]["b"]
    assert x["a"]["b"]["c"]["d"] == 1


class TestControlledDict:
    def test_add_allowed(self):
        dct = ControlledDict(a=1)
        dct._allow_add = True

        dct["b"] = 2
        assert dct["b"] == 2

        dct.update(d=3)
        assert dct["d"] == 3

        dct.setdefault("e", 4)
        assert dct["e"] == 4

    def test_add_disabled(self):
        dct = ControlledDict(a=1)
        dct._allow_add = False

        with pytest.raises(TypeError, match="add is disabled"):
            dct["b"] = 2

        with pytest.raises(TypeError, match="add is disabled"):
            dct.update(b=2)

        with pytest.raises(TypeError, match="add is disabled"):
            dct.setdefault("c", 2)

    def test_update_allowed(self):
        dct = ControlledDict(a=1)
        dct._allow_update = True

        dct["a"] = 2
        assert dct["a"] == 2

        dct.update({"a": 3})
        assert dct["a"] == 3

        # Test Iterator handling
        dct.update(zip(["c", "d"], [11, 12]))
        assert dct["c"] == 11

        dct.setdefault("a", 4)  # existing key
        assert dct["a"] == 3

    def test_update_disabled(self):
        dct = ControlledDict(a=1)
        dct._allow_update = False

        with pytest.raises(TypeError, match="update is disabled"):
            dct["a"] = 2

        with pytest.raises(TypeError, match="update is disabled"):
            dct.update({"a": 3})

        with pytest.raises(TypeError, match="update is disabled"):
            dct.setdefault("a", 4)

    def test_del_allowed(self):
        dct = ControlledDict(a=1, b=2, c=3, d=4)
        dct._allow_del = True

        del dct["a"]
        assert "a" not in dct

        val = dct.pop("b")
        assert val == 2 and "b" not in dct

        val = dct.popitem()
        assert val == ("c", 3) and "c" not in dct

        dct.clear()
        assert dct == {}

    def test_del_disabled(self):
        dct = ControlledDict(a=1)
        dct._allow_del = False

        with pytest.raises(TypeError, match="delete is disabled"):
            del dct["a"]

        with pytest.raises(TypeError, match="delete is disabled"):
            dct.pop("a")

        with pytest.raises(TypeError, match="delete is disabled"):
            dct.popitem()

        with pytest.raises(TypeError, match="delete is disabled"):
            dct.clear()

    def test_frozen_like(self):
        """Make sure add and update are allowed at init time."""
        ControlledDict._allow_add = False
        ControlledDict._allow_update = False

        dct = ControlledDict({"hello": "world"})
        assert isinstance(dct, UserDict)
        assert dct["hello"] == "world"

        assert not dct._allow_add
        assert not dct._allow_update

    def test_iterator_handling(self):
        """Make sure iterators are handling correctly."""
        c_dict = ControlledDict(zip(["c", "d"], [11, 12]))
        assert c_dict["c"] == 11


def test_frozendict():
    dct = frozendict({"hello": "world"})
    assert isinstance(dct, UserDict)
    assert dct["hello"] == "world"

    assert not dct._allow_add
    assert not dct._allow_update
    assert not dct._allow_del

    # Test setter
    with pytest.raises(TypeError, match="add is disabled"):
        dct["key"] = "val"

    # Test update
    with pytest.raises(TypeError, match="add is disabled"):
        dct.update(key="val")

    # Test pop
    with pytest.raises(TypeError, match="delete is disabled"):
        dct.pop("key")

    # Test delete
    with pytest.raises(TypeError, match="delete is disabled"):
        del dct["key"]


def test_namespace_dict():
    dct = Namespace(key="val")
    assert isinstance(dct, UserDict)

    # Test setter
    dct["hello"] = "world"
    assert dct["key"] == "val"

    # Test use `update` to add new values
    dct.update({"new_key": "new_value"})
    assert dct["new_key"] == "new_value"

    # Test add (not allowed)
    with pytest.raises(TypeError, match="update is disabled"):
        dct["key"] = "val"

    with pytest.raises(TypeError, match="update is disabled"):
        dct.update({"key": "val"})

    # Test delete (not allowed)
    with pytest.raises(TypeError, match="delete is disabled"):
        del dct["key"]


def test_attr_dict():
    dct = AttrDict(foo=1, bar=2)

    # Test get attribute
    assert dct.bar == 2
    assert dct["foo"] is dct.foo

    # Test key not found error
    with pytest.raises(KeyError, match="no-such-key"):
        dct["no-such-key"]

    # Test setter
    dct.bar = "hello"
    assert dct["bar"] == "hello"

    # Test delete
    del dct.bar
    assert "bar" not in dct

    # Test builtin dict method shadowing
    with pytest.warns(UserWarning, match="shadows dict method"):
        dct["update"] = "value"


def test_frozen_attrdict():
    dct = FrozenAttrDict({"hello": "world", 1: 2})
    assert isinstance(dct, UserDict)

    # Test attribute-like operations
    with pytest.raises(TypeError, match="does not support item assignment"):
        dct.foo = "bar"

    with pytest.raises(TypeError, match="does not support item assignment"):
        dct.hello = "new"

    with pytest.raises(TypeError, match="does not support item deletion"):
        del dct.hello

    # Test get value
    assert dct["hello"] == "world"
    assert dct.hello == "world"
    assert dct["hello"] is dct.hello  # identity check

    # Test adding item
    with pytest.raises(TypeError, match="add is disabled"):
        dct["foo"] = "bar"

    # Test modifying existing item
    with pytest.raises(TypeError, match="update is disabled"):
        dct["hello"] = "new"

    # Test update
    with pytest.raises(TypeError, match="update is disabled"):
        dct.update({"hello": "world"})

    # Test pop
    with pytest.raises(TypeError, match="delete is disabled"):
        dct.pop("hello")

    with pytest.raises(TypeError, match="delete is disabled"):
        dct.popitem()

    # Test delete
    with pytest.raises(TypeError, match="delete is disabled"):
        del dct["hello"]

    with pytest.raises(TypeError, match="delete is disabled"):
        dct.clear()


def test_mongo_dict():
    m_dct = MongoDict({"a": {"b": 1}, "x": 2})
    assert m_dct.a.b == 1
    assert m_dct.x == 2
    assert "a" in m_dct
    assert "b" in m_dct.a
    assert m_dct["a"] == {"b": 1}


def test_dict2namedtuple():
    # Init from dict
    tpl = dict2namedtuple(foo=1, bar="hello")
    assert isinstance(tpl, tuple)
    assert tpl.foo == 1 and tpl.bar == "hello"

    # Init from list of tuples
    tpl = dict2namedtuple([("foo", 1), ("bar", "hello")])
    assert isinstance(tpl, tuple)
    assert tpl[0] == 1
    assert tpl[1] == "hello"
    assert tpl[0] is tpl.foo and tpl[1] is tpl.bar