xref: /openbmc/openbmc/poky/bitbake/lib/bb/COW.py (revision 4ed12e16)
1#
2# This is a copy on write dictionary and set which abuses classes to try and be nice and fast.
3#
4# Copyright (C) 2006 Tim Ansell
5#
6#Please Note:
7# Be careful when using mutable types (ie Dict and Lists) - operations involving these are SLOW.
8# Assign a file to __warn__ to get warnings about slow operations.
9#
10
11
12import copy
13ImmutableTypes = (
14    bool,
15    complex,
16    float,
17    int,
18    tuple,
19    frozenset,
20    str
21)
22
23MUTABLE = "__mutable__"
24
25class COWMeta(type):
26    pass
27
28class COWDictMeta(COWMeta):
29    __warn__ = False
30    __hasmutable__ = False
31    __marker__ = tuple()
32
33    def __str__(cls):
34        # FIXME: I have magic numbers!
35        return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3)
36    __repr__ = __str__
37
38    def cow(cls):
39        class C(cls):
40            __count__ = cls.__count__ + 1
41        return C
42    copy = cow
43    __call__ = cow
44
45    def __setitem__(cls, key, value):
46        if value is not None and not isinstance(value, ImmutableTypes):
47            if not isinstance(value, COWMeta):
48                cls.__hasmutable__ = True
49            key += MUTABLE
50        setattr(cls, key, value)
51
52    def __getmutable__(cls, key, readonly=False):
53        nkey = key + MUTABLE
54        try:
55            return cls.__dict__[nkey]
56        except KeyError:
57            pass
58
59        value = getattr(cls, nkey)
60        if readonly:
61            return value
62
63        if not cls.__warn__ is False and not isinstance(value, COWMeta):
64            print("Warning: Doing a copy because %s is a mutable type." % key, file=cls.__warn__)
65        try:
66            value = value.copy()
67        except AttributeError as e:
68            value = copy.copy(value)
69        setattr(cls, nkey, value)
70        return value
71
72    __getmarker__ = []
73    def __getreadonly__(cls, key, default=__getmarker__):
74        """\
75        Get a value (even if mutable) which you promise not to change.
76        """
77        return cls.__getitem__(key, default, True)
78
79    def __getitem__(cls, key, default=__getmarker__, readonly=False):
80        try:
81            try:
82                value = getattr(cls, key)
83            except AttributeError:
84                value = cls.__getmutable__(key, readonly)
85
86            # This is for values which have been deleted
87            if value is cls.__marker__:
88                raise AttributeError("key %s does not exist." % key)
89
90            return value
91        except AttributeError as e:
92            if not default is cls.__getmarker__:
93                return default
94
95            raise KeyError(str(e))
96
97    def __delitem__(cls, key):
98        cls.__setitem__(key, cls.__marker__)
99
100    def __revertitem__(cls, key):
101        if key not in cls.__dict__:
102            key += MUTABLE
103        delattr(cls, key)
104
105    def __contains__(cls, key):
106        return cls.has_key(key)
107
108    def has_key(cls, key):
109        value = cls.__getreadonly__(key, cls.__marker__)
110        if value is cls.__marker__:
111            return False
112        return True
113
114    def iter(cls, type, readonly=False):
115        for key in dir(cls):
116            if key.startswith("__"):
117                continue
118
119            if key.endswith(MUTABLE):
120                key = key[:-len(MUTABLE)]
121
122            if type == "keys":
123                yield key
124
125            try:
126                if readonly:
127                    value = cls.__getreadonly__(key)
128                else:
129                    value = cls[key]
130            except KeyError:
131                continue
132
133            if type == "values":
134                yield value
135            if type == "items":
136                yield (key, value)
137        return
138
139    def iterkeys(cls):
140        return cls.iter("keys")
141    def itervalues(cls, readonly=False):
142        if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
143            print("Warning: If you arn't going to change any of the values call with True.", file=cls.__warn__)
144        return cls.iter("values", readonly)
145    def iteritems(cls, readonly=False):
146        if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
147            print("Warning: If you arn't going to change any of the values call with True.", file=cls.__warn__)
148        return cls.iter("items", readonly)
149
150class COWSetMeta(COWDictMeta):
151    def __str__(cls):
152        # FIXME: I have magic numbers!
153        return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) -3)
154    __repr__ = __str__
155
156    def cow(cls):
157        class C(cls):
158            __count__ = cls.__count__ + 1
159        return C
160
161    def add(cls, value):
162        COWDictMeta.__setitem__(cls, repr(hash(value)), value)
163
164    def remove(cls, value):
165        COWDictMeta.__delitem__(cls, repr(hash(value)))
166
167    def __in__(cls, value):
168        return repr(hash(value)) in COWDictMeta
169
170    def iterkeys(cls):
171        raise TypeError("sets don't have keys")
172
173    def iteritems(cls):
174        raise TypeError("sets don't have 'items'")
175
176# These are the actual classes you use!
177class COWDictBase(object, metaclass = COWDictMeta):
178    __count__ = 0
179
180class COWSetBase(object, metaclass = COWSetMeta):
181    __count__ = 0
182
183if __name__ == "__main__":
184    import sys
185    COWDictBase.__warn__ = sys.stderr
186    a = COWDictBase()
187    print("a", a)
188
189    a['a'] = 'a'
190    a['b'] = 'b'
191    a['dict'] = {}
192
193    b = a.copy()
194    print("b", b)
195    b['c'] = 'b'
196
197    print()
198
199    print("a", a)
200    for x in a.iteritems():
201        print(x)
202    print("--")
203    print("b", b)
204    for x in b.iteritems():
205        print(x)
206    print()
207
208    b['dict']['a'] = 'b'
209    b['a'] = 'c'
210
211    print("a", a)
212    for x in a.iteritems():
213        print(x)
214    print("--")
215    print("b", b)
216    for x in b.iteritems():
217        print(x)
218    print()
219
220    try:
221        b['dict2']
222    except KeyError as e:
223        print("Okay!")
224
225    a['set'] = COWSetBase()
226    a['set'].add("o1")
227    a['set'].add("o1")
228    a['set'].add("o2")
229
230    print("a", a)
231    for x in a['set'].itervalues():
232        print(x)
233    print("--")
234    print("b", b)
235    for x in b['set'].itervalues():
236        print(x)
237    print()
238
239    b['set'].add('o3')
240
241    print("a", a)
242    for x in a['set'].itervalues():
243        print(x)
244    print("--")
245    print("b", b)
246    for x in b['set'].itervalues():
247        print(x)
248    print()
249
250    a['set2'] = set()
251    a['set2'].add("o1")
252    a['set2'].add("o1")
253    a['set2'].add("o2")
254
255    print("a", a)
256    for x in a.iteritems():
257        print(x)
258    print("--")
259    print("b", b)
260    for x in b.iteritems(readonly=True):
261        print(x)
262    print()
263
264    del b['b']
265    try:
266        print(b['b'])
267    except KeyError:
268        print("Yay! deleted key raises error")
269
270    if 'b' in b:
271        print("Boo!")
272    else:
273        print("Yay - has_key with delete works!")
274
275    print("a", a)
276    for x in a.iteritems():
277        print(x)
278    print("--")
279    print("b", b)
280    for x in b.iteritems(readonly=True):
281        print(x)
282    print()
283
284    b.__revertitem__('b')
285
286    print("a", a)
287    for x in a.iteritems():
288        print(x)
289    print("--")
290    print("b", b)
291    for x in b.iteritems(readonly=True):
292        print(x)
293    print()
294
295    b.__revertitem__('dict')
296    print("a", a)
297    for x in a.iteritems():
298        print(x)
299    print("--")
300    print("b", b)
301    for x in b.iteritems(readonly=True):
302        print(x)
303    print()
304