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