Completed
Push — master ( cba13e...d3ddc5 )
by Satoru
54s
created

m9dicts.UpdateWithReplaceDict   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 50
Duplicated Lines 0 %
Metric Value
wmc 9
dl 0
loc 50
rs 10

2 Methods

Rating   Name   Duplication   Size   Complexity  
A _update() 0 7 2
C update() 0 22 7
1
#
2
# Copyright (C) 2011 - 2015 Red Hat, Inc.
3
# Copyright (C) 2011 - 2016 Satoru SATOH <ssato redhat.com>
4
# License: MIT
5
#
6
# .. note:: suppress warning about import error of ordereddict only for py-2.6.
7
# pylint: disable=import-error
8
"""Some dict-like classes support merge operations.
9
"""
10
from __future__ import absolute_import
11
12
import m9dicts.globals as MG
13
import m9dicts.utils
14
15
from m9dicts.compat import OrderedDict
16
17
18
def _are_list_like(*objs):
19
    """
20
    >>> _are_list_like([], (), [x for x in range(10)], (x for x in range(4)))
21
    True
22
    >>> _are_list_like([], {})
23
    False
24
    >>> _are_list_like([], "aaa")
25
    False
26
    """
27
    return all(m9dicts.utils.is_list_like(obj) for obj in objs)
28
29
30
class UpdateWithReplaceDict(dict):
31
    """
32
    Replace value of self with other's if both have same keys on update.
33
    Otherwise, just keep the value of self.
34
35
    >>> od0 = OrderedDict((("a", 1), ("b", [1, 3]), ("c", "abc"), ("f", None)))
36
    >>> md0 = UpdateWithReplaceDict(od0)
37
    >>> upd = UpdateWithReplaceDict(a=2, b=[0, 1], c=dict(d="d", e=1), d="d")
38
    >>> (ref, md1, md2, md3) = [md0.copy() for _ in range(4)]
39
    >>> md0.update(upd)
40
    >>> md1.update(dict(upd))
41
    >>> md2.update(list(upd.items()))
42
    >>> md3.update(**upd)
43
    >>> for md in (md0, md1, md2, md3):
44
    ...     assert all(md[k] == upd[k] for k in upd.keys())
45
    ...     assert all(md[k] == ref[k] for k in ref.keys() if k not in upd)
46
    >>> md5 = UpdateWithReplaceDict(1)  # doctest: +ELLIPSIS
47
    Traceback (most recent call last):
48
    TypeError: ...
49
    """
50
    def _update(self, other, key, *args):
51
        """
52
        :param other: a dict[-like] object or a list of (key, value) tuples
53
        :param key: object key
54
        :param args: [] or (value, ...)
55
        """
56
        self[key] = args[0] if args else other[key]
57
58
    def update(self, *others, **another):
59
        """
60
        :param others:
61
            a list of dict[-like] objects or a list of (key, value) tuples to
62
            update self
63
        :param another: optional keyword arguments to update self more
64
65
        .. seealso:: Document of dict.update
66
        """
67
        for other in others:
68
            if hasattr(other, "keys"):
69
                for key in other.keys():
70
                    self._update(other, key)
71
            else:
72
                try:
73
                    for key, val in other:
74
                        self._update(other, key, val)
75
                except (ValueError, TypeError) as exc:
76
                    raise type(exc)(str(exc) + " other=%r" % other)
77
78
        for key in another.keys():
79
            self._update(another, key)
80
81
82
class UpdateWoReplaceDict(UpdateWithReplaceDict):
83
    """
84
    Never update (replace) the value of self with other's, that is, only the
85
    values self does not have its key will be added on update.
86
87
    >>> od0 = OrderedDict((("a", 1), ("b", [1, 3]), ("c", "abc"),
88
    ...                    ("f", None)))
89
    >>> md0 = UpdateWoReplaceDict(od0)
90
    >>> ref = md0.copy()
91
    >>> upd = UpdateWoReplaceDict(a=2, b=[0, 1], c="xyz", d=None)
92
    >>> md0.update(upd)
93
    >>> all(md0[k] == upd[k] for k in upd.keys() if k not in ref)
94
    True
95
    >>> all(md0[k] == ref[k] for k in ref.keys())
96
    True
97
    """
98
    def _update(self, other, key, *args):
99
        """
100
        :param other: a dict[-like] object or a list of (key, value) tuples
101
        :param key: object key
102
        :param args: [] or (value, ...)
103
        """
104
        if key not in self:
105
            self[key] = args[0] if args else other[key]
106
107
108
class UpdateWithMergeDict(UpdateWithReplaceDict):
109
    """
110
    Merge the value of self with other's recursively. Behavior of merge will be
111
    vary depends on types of original and new values.
112
113
    - dict-like vs. dict-like -> merge recursively
114
    - list vs. list -> vary depends on `merge_lists`. see its description.
115
    - other objects vs. any -> vary depends on `keep`. see its description.
116
117
    class variables:
118
119
        - merge_lists: Merge not only dicts but also lists. For example,
120
121
            [1, 2, 3], [3, 4] ==> [1, 2, 3, 4]
122
            [1, 2, 2], [2, 4] ==> [1, 2, 2, 4]
123
124
        - keep: Keep original value if type of original value is not a dict nor
125
          list. It will be simply replaced with new value by default.
126
127
    >>> od0 = OrderedDict((("c", 2), ("d", 3)))
128
    >>> od1 = OrderedDict((("c", 4), ("d", 5), ("g", None)))
129
    >>> md0 = UpdateWithMergeDict((("a", 1), ("b", UpdateWithMergeDict(od0)),
130
    ...                            ("e", [1, 2, 2]), ("f", None)))
131
    >>> upd = dict(a=2, b=od1, e=[2, 3, 4])
132
    >>> ref = md0.copy()
133
    >>> md0.update(upd)
134
    >>> all(md0[k] == upd[k] for k in ("a", "e"))  # vary depends on 'keep'.
135
    True
136
    >>> all(md0["b"][k] == ref["b"][k] for k in ref["b"].keys())
137
    True
138
    >>> all(md0[k] == ref[k] for k in ref.keys() if k not in upd)
139
    True
140
    >>> class Foo(UpdateWithMergeDict):
141
    ...     keep = True
142
    >>> md1 = Foo(a=1, b=2)
143
    >>> md1.update(a=2, c=3)
144
    >>> md1["a"]
145
    1
146
    >>> md1["c"]
147
    3
148
    """
149
    merge_lists = False
150
    keep = False
151
152
    def _update(self, other, key, *args):
153
        """
154
        :param other: a dict[-like] object or a list of (key, value) tuples
155
        :param key: object key
156
        :param args: [] or (value, ...)
157
        """
158
        val = args[0] if args else other[key]
159
        if key in self:
160
            val0 = self[key]  # Original value
161
            if m9dicts.utils.is_dict_like(val0):  # It needs recursive updates.
162
                self[key].update(val)
163
            elif self.merge_lists and _are_list_like(val, val0):
164
                self[key] += [x for x in val if x not in val0]
165
            elif not self.keep:
166
                self[key] = val  # Overwrite it.
167
        else:
168
            self[key] = val
169
170
171
class UpdateWithMergeListsDict(UpdateWithMergeDict):
172
    """
173
    Similar to UpdateWithMergeDict but merge lists by default.
174
175
    >>> md0 = UpdateWithMergeListsDict(a=[1, 2, 3])
176
    >>> md0.update(a=[4, 4, 5])
177
    >>> md0["a"]
178
    [1, 2, 3, 4, 4, 5]
179
    """
180
    merge_lists = True
181
182
183
class UpdateWithReplaceOrderedDict(UpdateWithReplaceDict, OrderedDict):
184
    """
185
    Similar to UpdateWithReplaceDict but keep keys' order like OrderedDict.
186
187
    >>> od0 = OrderedDict((("a", 1), ("b", [1, 3]), ("c", "abc"), ("f", None)))
188
    >>> od1 = OrderedDict((("a", 2), ("b", [0, 1]),
189
    ...                    ("c", OrderedDict((("d", "d"), ("e", 1)))),
190
    ...                    ("d", "d")))
191
    >>> md0 = UpdateWithReplaceOrderedDict(od0)
192
    >>> md1 = UpdateWithReplaceOrderedDict(od0.items())
193
    >>> upd = UpdateWithReplaceOrderedDict(od1)
194
    >>> ref = md0.copy()
195
    >>> for md in (md0, md1):
196
    ...     md.update(upd)
197
    ...     assert all(md0[k] == upd[k] for k in upd.keys())
198
    ...     assert all(md0[k] == ref[k] for k in ref.keys() if k not in upd)
199
    ...     assert list(md0.keys()) == ['a', 'b', 'c', 'f', 'd']
200
    >>>
201
    """
202
    pass
203
204
205
class UpdateWoReplaceOrderedDict(UpdateWoReplaceDict, OrderedDict):
206
    """
207
    Similar to UpdateWoReplaceDict but keep keys' order like OrderedDict.
208
209
    >>> od0 = OrderedDict((("a", 1), ("b", [1, 3]), ("c", "abc"),
210
    ...                    ("f", None)))
211
    >>> md0 = UpdateWoReplaceOrderedDict(od0)
212
    >>> upd = UpdateWoReplaceOrderedDict((("a", 2), ("b", [0, 1]),
213
    ...                                   ("c", "xyz"), ("d", None)))
214
    >>> ref = md0.copy()
215
    >>> md0.update(upd)
216
    >>> all(md0[k] == upd[k] for k in upd.keys() if k not in ref)
217
    True
218
    >>> all(md0[k] == ref[k] for k in ref.keys())
219
    True
220
    >>> list(md0.keys())
221
    ['a', 'b', 'c', 'f', 'd']
222
    """
223
    pass
224
225
226
class UpdateWithMergeOrderedDict(UpdateWithMergeDict, OrderedDict):
227
    """
228
    Similar to UpdateWithMergeDict but keep keys' order like OrderedDict.
229
230
    >>> od0 = OrderedDict((("c", 2), ("d", 3)))
231
    >>> od1 = OrderedDict((("c", 4), ("d", 5), ("g", None)))
232
    >>> md0 = UpdateWithMergeOrderedDict(
233
    ...          OrderedDict((("a", 1),
234
    ...                       ("b", UpdateWithMergeOrderedDict(od0)),
235
    ...                       ("e", [1, 2, 2]), ("f", None))))
236
    >>> ref = md0.copy()
237
    >>> upd = dict(a=2, b=UpdateWithMergeOrderedDict(od1), e=[2, 3, 4])
238
    >>> md0.update(upd)
239
    >>> all(md0[k] == upd[k] for k in ("a", "e"))
240
    True
241
    >>> all(md0[k] == ref[k] for k in ref.keys() if k not in upd)
242
    True
243
    >>> all(md0["b"][k] == ref["b"][k] for k in ref["b"].keys())
244
    True
245
    >>> list(md0.keys())
246
    ['a', 'b', 'e', 'f']
247
    >>> list(md0["b"].keys())
248
    ['c', 'd', 'g']
249
    """
250
    pass
251
252
253
class UpdateWithMergeListsOrderedDict(UpdateWithMergeListsDict, OrderedDict):
254
    """
255
    Similar to UpdateWithMergeListsDict but keep keys' order like OrderedDict.
256
257
    >>> md0 = UpdateWithMergeListsOrderedDict((("a", [1, 2, 3]), ('b', 0)))
258
    >>> md0.update(a=[4, 4, 5])
259
    >>> md0["a"]
260
    [1, 2, 3, 4, 4, 5]
261
    >>> list(md0.keys())
262
    ['a', 'b']
263
    """
264
    pass
265
266
267
# Mapppings: (merge_strategy, ordered?) : m9dict class
268
_MDICTS_MAP = {(MG.MS_REPLACE, True): UpdateWithReplaceOrderedDict,
269
               (MG.MS_REPLACE, False): UpdateWithReplaceDict,
270
               (MG.MS_NO_REPLACE, True): UpdateWoReplaceOrderedDict,
271
               (MG.MS_NO_REPLACE, False): UpdateWoReplaceDict,
272
               (MG.MS_DICTS, True): UpdateWithMergeOrderedDict,
273
               (MG.MS_DICTS, False): UpdateWithMergeDict,
274
               (MG.MS_DICTS_AND_LISTS, True): UpdateWithMergeListsOrderedDict,
275
               (MG.MS_DICTS_AND_LISTS, False): UpdateWithMergeListsDict}
276
277
278
def get_mdict_class(merge=MG.MS_DICTS, ordered=False):
279
    """
280
    Select dict-like class based on merge strategy and orderness of keys.
281
282
    :param merge: Specify strategy from MERGE_STRATEGIES of how to merge dicts.
283
    :param ordered:
284
        The class keeps the order of insertion keys such as
285
        UpdateWoReplaceOrderedDict will be chosen if True.
286
287
    :return: The dict class object
288
    """
289
    return _MDICTS_MAP.get((merge, ordered), UpdateWithMergeDict)
290
291
# vim:sw=4:ts=4:et:
292