forked from autotest/virt-test
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpropcan.py
261 lines (197 loc) · 7.92 KB
/
propcan.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
"""
Class which allows property and dict-like access to a fixed set of instance
attributes. Attributes are locked by __slots__, however accessor methods
may be created/removed on instances, or defined by the subclass. An
INITIALIZED attribute is provided to signel completion of __init__()
for use by accessor methods (i.e. so they know when __init__ may be
setting values).
Subclasses must define a __slots__ class attribute containing the list
of attribute names to reserve. All additional subclass descendents
must explicitly copy __slots__ from the parent in their definition.
Users of subclass instances are expected to get/set/del attributes
only via the standard object or dict-like interface. i.e.
instance.attribute = whatever
or
instance['attribute'] = whatever
Internally, methods are free to call the accessor methods. Only
accessor methods should use the special dict_*() and super_*() methods.
These are there to allow convenient access to the internal dictionary
values and subclass-defined attributes (such as __slots__).
"""
class PropCanInternal(object):
"""
Semi-private methods for use only by PropCanBase subclasses (NOT instances)
"""
# The following methods are intended for use by accessor-methods
# where they may need to bypass the special attribute/key handling
def dict_get(self, key):
"""
Get a key unconditionally, w/o checking for accessor method or __slots__
"""
return dict.__getitem__(self, key)
def dict_set(self, key, value):
"""
Set a key unconditionally, w/o checking for accessor method or __slots__
"""
dict.__setitem__(self, key, value)
def dict_del(self, key):
"""
Del key unconditionally, w/o checking for accessor method or __slots__
"""
return dict.__delitem__(self, key)
def super_get(self, key):
"""
Get attribute unconditionally, w/o checking accessor method or __slots__
"""
return object.__getattribute__(self, key)
def super_set(self, key, value):
"""
Set attribute unconditionally, w/o checking accessor method or __slots__
"""
object.__setattr__(self, key, value)
def super_del(self, key):
"""
Del attribute unconditionally, w/o checking accessor method or __slots__
"""
object.__delattr__(self, key)
class PropCanBase(dict, PropCanInternal):
"""
Objects with optional accessor methods and dict-like access to fixed set of keys
"""
def __new__(cls, *args, **dargs):
if not hasattr(cls, '__slots__'):
raise NotImplementedError("Class '%s' must define __slots__ "
"property" % str(cls))
newone = dict.__new__(cls, *args, **dargs)
# Let accessor methods know initialization is running
newone.super_set('INITIALIZED', False)
return newone
def __init__(self, *args, **dargs):
"""
Initialize contents directly or by way of accessors
@param: *args: Initial values for __slots__ keys, same as dict.
@param: **dargs: Initial values for __slots__ keys, same as dict.
"""
# Params are initialized here, not in super
super(PropCanBase, self).__init__()
# No need to re-invent dict argument processing
values = dict(*args, **dargs)
for key in self.__slots__:
value = values.get(key, "@!@!@!@!@!SENTENEL!@!@!@!@!@")
if value is not "@!@!@!@!@!SENTENEL!@!@!@!@!@":
# Call accessor methods if present
self[key] = value
# Let accessor methods know initialization is complete
self.super_set('INITIALIZED', True)
def __getitem__(self, key):
try:
accessor = super(PropCanBase,
self).__getattribute__('get_%s' % key)
except AttributeError:
return super(PropCanBase, self).__getitem__(key)
return accessor()
def __setitem__(self, key, value):
self.__canhaz__(key, KeyError)
try:
accessor = super(PropCanBase,
self).__getattribute__('set_%s' % key)
except AttributeError:
return super(PropCanBase, self).__setitem__(key, value)
return accessor(value)
def __delitem__(self, key):
try:
accessor = super(PropCanBase,
self).__getattribute__('del_%s' % key)
except AttributeError:
return super(PropCanBase, self).__delitem__(key)
return accessor()
def __getattr__(self, key):
try:
# Attempt to call accessor methods first whenever possible
self.__canhaz__(key, KeyError)
return self.__getitem__(key)
except KeyError:
# Allow subclasses to define attributes if required
return super(PropCanBase, self).__getattribute__(key)
def __setattr__(self, key, value):
self.__canhaz__(key)
try:
return self.__setitem__(key, value)
except KeyError, detail:
# Prevent subclass instances from defining normal attributes
raise AttributeError(str(detail))
def __delattr__(self, key):
self.__canhaz__(key)
try:
return self.__delitem__(key)
except KeyError, detail:
# Prevent subclass instances from deleting normal attributes
raise AttributeError(str(detail))
def __canhaz__(self, key, excpt=AttributeError):
"""
Quickly determine if an accessor or instance attribute name is defined.
"""
slots = tuple(super(PropCanBase, self).__getattribute__('__slots__'))
keys = slots + ('get_%s' % key, 'set_%s' % key, 'del_%s' % key)
if key not in keys:
raise excpt("Key '%s' not found in super class attributes or in %s"
% (str(key), str(keys)))
def copy(self):
"""
Copy properties by value, not by reference.
"""
return self.__class__(dict(self))
class PropCan(PropCanBase):
"""
Special value handling on retrieval of None values
"""
def __len__(self):
length = 0
for key in self.__slots__:
# special None/False value handling
if self.__contains__(key):
length += 1
return length
def __contains__(self, key):
try:
value = self.dict_get(key)
except (KeyError, AttributeError):
return False
# Avoid inf. recursion if value == self
if issubclass(type(value), type(self)) or value:
return True
return False
def __eq__(self, other):
# special None/False value handling
return dict([(key, value) for key, value in self.items()]) == other
def __ne__(self, other):
return not self.__eq__(other)
def keys(self):
# special None/False value handling
return [key for key in self.__slots__ if self.__contains__(key)]
def values(self):
# special None/False value handling
return [self[key] for key in self.keys()]
def items(self):
return tuple( [(key, self[key]) for key in self.keys()] )
has_key = __contains__
def set_if_none(self, key, value):
"""
Set the value of key, only if it's not set or None
"""
if not self.has_key(key):
self[key] = value
def set_if_value_not_none(self, key, value):
"""
Set the value of key, only if value is not None
"""
if value:
self[key] = value
def __str__(self):
"""
Guarantee return of string format dictionary representation
"""
acceptable_types = (str, unicode, int, float, long)
return str( dict([(key, value) for key, value in self.items()
if issubclass(type(value), acceptable_types)]) )
__repr__ = __str__