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