Degrades to copy on mutable types - Cheap is the mutable types are already COW.
[bitbake.git] / lib / bb / COW.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 """
4 This is a copy on write dictionary which abuses classes to be nice and fast.
5
6 Please Note: Be careful when using mutable types (ie Dict and Lists). The copy on write stuff only kicks in on Assignment.
7 """
8
9 from inspect import getmro
10
11 import copy
12 import types, sets
13 types.ImmutableTypes = tuple([ \
14     types.BooleanType, \
15     types.ComplexType, \
16     types.FloatType, \
17     types.IntType, \
18     types.LongType, \
19     types.NoneType, \
20     types.TupleType, \
21     sets.ImmutableSet] + \
22     list(types.StringTypes))
23
24 MUTABLE = "_mutable__"
25  
26 class COWDictMeta(type):
27     def __str__(cls):
28         return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__))
29     __repr__ = __str__
30
31     def cow(cls):
32         class C(cls):
33             __count__ = cls.__count__ + 1
34         return C
35
36     def __setitem__(cls, key, value):
37         if not isinstance(value, types.ImmutableTypes):
38             key += MUTABLE
39         setattr(cls, key, value)
40     
41     def __getmutable__(cls, key):
42         """
43         This gets called when you do a "o.b" and b doesn't exist on o.
44
45         IE When the type is mutable.
46         """
47         nkey = key + MUTABLE
48         # Do we already have a copy of this on us?
49         if nkey in cls.__dict__:
50             return cls.__dict__[nkey]
51
52         r = getattr(cls, nkey)
53         try:
54             r = r.copy()
55         except NameError, e:
56             r = copy.copy(r)
57         setattr(cls, nkey, r)
58         return r
59
60     def __getitem__(cls, key):
61         try:
62             try:
63                 return getattr(cls, key)
64             except AttributeError:
65                 # Degrade to slow mode if type is not immutable, 
66                 # If the type is also a COW this will be cheap anyway
67                 return cls.__getmutable__(key)
68         except AttributeError, e:
69             raise KeyError(e)
70
71     def has_key(cls, key):
72         return (hasattr(cls, key) or hasattr(cls, key + MUTABLE))
73
74     def iter(cls, type):
75         for key in dir(cls):
76             if key.startswith("__"):
77                     continue
78
79             if key.endswith(MUTABLE):
80                 key = key[:-len(MUTABLE)]
81
82             if type == "keys":
83                 yield key
84             if type == "values":
85                 yield cls[key]
86             if type == "items":
87                 yield (key, cls[key])
88         raise StopIteration()
89
90     def iterkeys(cls):
91         return cls.iter("keys")
92     def itervalues(cls):
93         return cls.iter("values")
94     def iteritems(cls):
95         return cls.iter("items")
96     copy = cow
97
98 class COWSetMeta(COWDictMeta):
99     def __str__(cls):
100         return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__))
101     __repr__ = __str__
102
103     def cow(cls):
104         class C(cls):
105             __count__ = cls.__count__ + 1
106         return C
107
108     def add(cls, key, value):
109         cls.__setitem__(hash(key), value)
110
111 class COWDictBase(object):
112     __metaclass__ = COWDictMeta
113     __count__ = 0
114
115
116 if __name__ == "__main__":
117     a = COWDictBase.copy()
118     print a
119     a['a'] = 'a'
120     a['dict'] = {}
121
122     print "ha"
123     hasattr(a, "a")
124     print "ha"
125     
126     b = a.copy()
127     print b
128     b['b'] = 'b'
129
130     for x in a.iteritems():
131         print x
132     print "--"
133     for x in b.iteritems():
134         print x
135     print
136
137     b['dict']['a'] = 'b'
138     b['a'] = 'c'
139
140     for x in a.iteritems():
141         print x
142     print "--"
143     for x in b.iteritems():
144         print x
145     print
146
147     try:
148         b['dict2']
149     except KeyError, e:
150         print "Okay!"
151
152