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