forked from avocado-framework/avocado-vt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
propcan.py
341 lines (272 loc) · 10.7 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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
"""
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__).
example:
::
class A(PropCan):
# Class with *attributes*
__slots__ = ('a', 'b')
# 'a' has defined a set/get/del by definition of method with prefix
# set_a, get_a, del_a
# 'b' doesn't have defined set/get/del then classic set/get/del will be
# called instead.
def __init__(self, a=1, b='b'):
super(A, self).__init__(a, b)
def set_a(self, value)
# If is_instance(obj, A) then obj.a = "val" call this method.
self.__dict_set__("a", value)
def get_a(self, value)
# If is_instance(obj, A) then xx = obj.a call this method.
return self.__dict_get__("a")
def del_a(self, value)
# If is_instance(obj, A) then del obj.a call this method.
self.__dict_del__("a")
class B(PropCan):
# Class without *attributes*
# ***** Even if class doesn't have attributes there should be
# defined __slots__ = []. Because it is preferred by new style of class.
# *****
__slots__ = []
def __init__(self):
super(B, self).__init__()
"""
class PropCanInternal(object):
"""
Semi-private methods for use only by PropCanBase subclasses (NOT instances)
"""
#: Store all slots for debugging purposes
__all_slots__ = None
# 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 classproperty(property):
def __get__(self, obj, type_):
data = self.fget.__get__(None, type_)()
return data
def __set__(self, obj, value):
cls = type(obj)
return self.fset.__get__(None, cls)(value)
class PropCanBase(dict, PropCanInternal):
"""
Objects with optional accessor methods and dict-like access to fixed set of keys
"""
# get_*(), set_*(), del_*() accessor methods called from subclass
# __init__ sometimes need special handling, this is the signal.
INITIALIZED = False
# Help debugging by making all slot values available in all subclasses
# cache the value on first call
___all_slots__ = None
@classproperty
@classmethod
def __all_slots__(cls):
if not cls.___all_slots__:
all_slots = []
for cls_slots in [getattr(_cls, '__slots__', [])
for _cls in cls.__mro__]:
all_slots += cls_slots
cls.___all_slots__ = tuple(all_slots)
return cls.___all_slots__
def __new__(cls, *args, **dargs):
if not hasattr(cls, '__slots__'):
raise NotImplementedError("Class '%s' must define __slots__ "
"property" % str(cls))
newone = super(PropCanBase, cls).__new__(cls, *args, **dargs)
cls.___all_slots__ = tuple()
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.__all_slots__:
value = values.get(key, "@!@!@!SENTINEL!@!@!@")
if value is not "@!@!@!SENTINEL!@!@!@":
# 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 __get__(self, key, objtype=None):
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 __set__(self, key, value):
self.__canhaz__(key)
try:
return self.__setitem__(key, value)
except KeyError as detail:
# Prevent subclass instances from defining normal attributes
raise AttributeError(str(detail))
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 as 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 as 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 = self.__all_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))
def update(self, other=None, excpt=AttributeError, **kwargs):
"""
Update properties in __all_slots__ with another dict.
"""
_tmp_dict = dict()
_other_dict = dict()
if other:
_other_dict = dict(other)
try:
_tmp_dict.update(_other_dict)
_tmp_dict.update(kwargs)
except TypeError as detail:
raise excpt(detail)
for item in _tmp_dict.keys():
self[item] = _tmp_dict[item]
class PropCan(PropCanBase):
"""
Special value handling on retrieval of None values
"""
def __len__(self):
length = 0
for key in self.__all_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.__all_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 key not in self:
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