xref: /openbmc/openbmc/poky/bitbake/lib/bb/COW.py (revision 12fc939c)
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
13
14ImmutableTypes = (
15    bool,
16    complex,
17    float,
18    int,
19    tuple,
20    frozenset,
21    str
22)
23
24MUTABLE = "__mutable__"
25
26
27class COWMeta(type):
28    pass
29
30
31class COWDictMeta(COWMeta):
32    __warn__ = False
33    __hasmutable__ = False
34    __marker__ = tuple()
35
36    def __str__(cls):
37        # FIXME: I have magic numbers!
38        return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3)
39
40    __repr__ = __str__
41
42    def cow(cls):
43        class C(cls):
44            __count__ = cls.__count__ + 1
45
46        return C
47
48    copy = cow
49    __call__ = cow
50
51    def __setitem__(cls, key, value):
52        if value is not None and not isinstance(value, ImmutableTypes):
53            if not isinstance(value, COWMeta):
54                cls.__hasmutable__ = True
55            key += MUTABLE
56        setattr(cls, key, value)
57
58    def __getmutable__(cls, key, readonly=False):
59        nkey = key + MUTABLE
60        try:
61            return cls.__dict__[nkey]
62        except KeyError:
63            pass
64
65        value = getattr(cls, nkey)
66        if readonly:
67            return value
68
69        if not cls.__warn__ is False and not isinstance(value, COWMeta):
70            print("Warning: Doing a copy because %s is a mutable type." % key, file=cls.__warn__)
71        try:
72            value = value.copy()
73        except AttributeError as e:
74            value = copy.copy(value)
75        setattr(cls, nkey, value)
76        return value
77
78    __getmarker__ = []
79
80    def __getreadonly__(cls, key, default=__getmarker__):
81        """
82        Get a value (even if mutable) which you promise not to change.
83        """
84        return cls.__getitem__(key, default, True)
85
86    def __getitem__(cls, key, default=__getmarker__, readonly=False):
87        try:
88            try:
89                value = getattr(cls, key)
90            except AttributeError:
91                value = cls.__getmutable__(key, readonly)
92
93            # This is for values which have been deleted
94            if value is cls.__marker__:
95                raise AttributeError("key %s does not exist." % key)
96
97            return value
98        except AttributeError as e:
99            if not default is cls.__getmarker__:
100                return default
101
102            raise KeyError(str(e))
103
104    def __delitem__(cls, key):
105        cls.__setitem__(key, cls.__marker__)
106
107    def __revertitem__(cls, key):
108        if key not in cls.__dict__:
109            key += MUTABLE
110        delattr(cls, key)
111
112    def __contains__(cls, key):
113        return cls.has_key(key)
114
115    def has_key(cls, key):
116        value = cls.__getreadonly__(key, cls.__marker__)
117        if value is cls.__marker__:
118            return False
119        return True
120
121    def iter(cls, type, readonly=False):
122        for key in dir(cls):
123            if key.startswith("__"):
124                continue
125
126            if key.endswith(MUTABLE):
127                key = key[:-len(MUTABLE)]
128
129            if type == "keys":
130                yield key
131
132            try:
133                if readonly:
134                    value = cls.__getreadonly__(key)
135                else:
136                    value = cls[key]
137            except KeyError:
138                continue
139
140            if type == "values":
141                yield value
142            if type == "items":
143                yield (key, value)
144        return
145
146    def iterkeys(cls):
147        return cls.iter("keys")
148
149    def itervalues(cls, readonly=False):
150        if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
151            print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__)
152        return cls.iter("values", readonly)
153
154    def iteritems(cls, readonly=False):
155        if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
156            print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__)
157        return cls.iter("items", readonly)
158
159
160class COWSetMeta(COWDictMeta):
161    def __str__(cls):
162        # FIXME: I have magic numbers!
163        return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3)
164
165    __repr__ = __str__
166
167    def cow(cls):
168        class C(cls):
169            __count__ = cls.__count__ + 1
170
171        return C
172
173    def add(cls, value):
174        COWDictMeta.__setitem__(cls, repr(hash(value)), value)
175
176    def remove(cls, value):
177        COWDictMeta.__delitem__(cls, repr(hash(value)))
178
179    def __in__(cls, value):
180        return repr(hash(value)) in COWDictMeta
181
182    def iterkeys(cls):
183        raise TypeError("sets don't have keys")
184
185    def iteritems(cls):
186        raise TypeError("sets don't have 'items'")
187
188
189# These are the actual classes you use!
190class COWDictBase(metaclass=COWDictMeta):
191    __count__ = 0
192
193
194class COWSetBase(metaclass=COWSetMeta):
195    __count__ = 0
196