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
|
# attributes.py - manages object attributes
# Copyright (C) 2005 Michael Bayer mike_mp@zzzcomputing.com
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
import sqlalchemy.util as util
import weakref
class SmartProperty(object):
"""attaches AttributeManager functionality to the property accessors of a class. all
instances of the class will retrieve and modify their properties via an
AttributeManager."""
def __init__(self, manager):
self.manager = manager
def attribute_registry(self):
return self.manager
def property(self, key, uselist):
def set_prop(obj, value):
self.attribute_registry().set_attribute(obj, key, value)
def del_prop(obj):
self.attribute_registry().delete_attribute(obj, key)
def get_prop(obj):
if uselist:
return self.attribute_registry().get_list_attribute(obj, key)
else:
return self.attribute_registry().get_attribute(obj, key)
return property(get_prop, set_prop, del_prop)
class PropHistory(object):
"""manages the value of a particular scalar attribute on a particular object instance."""
# make our own NONE to distinguish from "None"
NONE = object()
def __init__(self, obj, key, backrefmanager=None, **kwargs):
self.obj = obj
self.key = key
self.orig = PropHistory.NONE
self.backrefmanager = backrefmanager
def gethistory(self, *args, **kwargs):
return self
def history_contains(self, obj):
return self.orig is obj or self.obj.__dict__[self.key] is obj
def setattr_clean(self, value):
self.obj.__dict__[self.key] = value
def getattr(self):
return self.obj.__dict__[self.key]
def setattr(self, value):
if isinstance(value, list):
raise ("assigning a list to scalar property '%s' on '%s' instance %d" % (self.key, self.obj.__class__.__name__, id(self.obj)))
self.orig = self.obj.__dict__.get(self.key, None)
self.obj.__dict__[self.key] = value
if self.backrefmanager is not None and self.orig is not value:
self.backrefmanager.set(self.obj, value, self.orig)
def delattr(self):
self.orig = self.obj.__dict__.get(self.key, None)
self.obj.__dict__[self.key] = None
if self.backrefmanager is not None:
self.backrefmanager.set(self.obj, None, self.orig)
def rollback(self):
if self.orig is not PropHistory.NONE:
self.obj.__dict__[self.key] = self.orig
self.orig = PropHistory.NONE
def commit(self):
self.orig = PropHistory.NONE
def added_items(self):
if self.orig is not PropHistory.NONE:
return [self.obj.__dict__[self.key]]
else:
return []
def deleted_items(self):
if self.orig is not PropHistory.NONE and self.orig is not None:
return [self.orig]
else:
return []
def unchanged_items(self):
if self.orig is PropHistory.NONE:
return [self.obj.__dict__[self.key]]
else:
return []
class ListElement(util.HistoryArraySet):
"""manages the value of a particular list-based attribute on a particular object instance."""
def __init__(self, obj, key, data=None, backrefmanager=None, **kwargs):
self.obj = obj
self.key = key
self.backrefmanager = backrefmanager
# if we are given a list, try to behave nicely with an existing
# list that might be set on the object already
try:
list_ = obj.__dict__[key]
if data is not None:
for d in data:
list_.append(d)
except KeyError:
if data is not None:
list_ = data
else:
list_ = []
obj.__dict__[key] = []
util.HistoryArraySet.__init__(self, list_, readonly=kwargs.get('readonly', False))
def gethistory(self, *args, **kwargs):
return self
def list_value_changed(self, obj, key, item, listval, isdelete):
pass
def setattr(self, value):
self.obj.__dict__[self.key] = value
self.set_data(value)
def delattr(self, value):
pass
def _setrecord(self, item):
res = util.HistoryArraySet._setrecord(self, item)
if res:
self.list_value_changed(self.obj, self.key, item, self, False)
if self.backrefmanager is not None:
self.backrefmanager.append(self.obj, item)
return res
def _delrecord(self, item):
res = util.HistoryArraySet._delrecord(self, item)
if res:
self.list_value_changed(self.obj, self.key, item, self, True)
if self.backrefmanager is not None:
self.backrefmanager.delete(self.obj, item)
return res
class CallableProp(object):
"""allows the attaching of a callable item, representing the future value
of a particular attribute on a particular object instance, to
the AttributeManager. When the attributemanager
accesses the object attribute, either to get its history or its real value, the __call__ method
is invoked which runs the underlying callable_ and sets the new value to the object attribute
via the manager, at which point the CallableProp itself is dereferenced."""
def __init__(self, manager, callable_, obj, key, uselist = False, live = False, **kwargs):
self.manager = manager
self.callable_ = callable_
self.obj = obj
self.key = key
self.uselist = uselist
self.live = live
self.kwargs = kwargs
def gethistory(self, passive=False, *args, **kwargs):
if not self.uselist:
if self.obj.__dict__.get(self.key, None) is None:
if passive:
value = None
else:
value = self.callable_()
self.obj.__dict__[self.key] = value
p = PropHistory(self.obj, self.key, **self.kwargs)
else:
if self.live or not self.obj.__dict__.has_key(self.key) or len(self.obj.__dict__[self.key]) == 0:
if passive:
value = None
else:
value = self.callable_()
else:
value = None
p = self.manager.create_list(self.obj, self.key, value, readonly=self.live, **self.kwargs)
if not self.live:
# set the new history list as the new attribute, discards ourself
self.manager.attribute_history(self.obj)[self.key] = p
self.manager = None
# unless we are "live", in which case we stay around to execute again
return p
def commit(self):
pass
def rollback(self):
pass
class BackrefManager(object):
def __init__(self, key):
self.key = key
def append(self, parent, child):
pass
def delete(self, parent, child):
pass
def set(self, parent, child, oldchild):
pass
class ListBackrefManager(BackrefManager):
def append(self, parent, child):
getattr(child, self.key).append(parent)
def delete(self, parent, child):
getattr(child, self.key).remove(parent)
class OneToManyBackrefManager(BackrefManager):
def append(self, parent, child):
setattr(child, self.key, parent)
def delete(self, parent, child):
setattr(child, self.key, None)
class ManyToOneBackrefManager(BackrefManager):
def set(self, parent, child, oldchild):
if oldchild is not None:
try:
getattr(oldchild, self.key).remove(parent)
except:
print "wha? oldchild is ", repr(oldchild)
if child is not None:
getattr(child, self.key).append(parent)
class AttributeManager(object):
"""maintains a set of per-attribute callable/history manager objects for a set of objects."""
def __init__(self):
pass
def value_changed(self, obj, key, value):
"""subclasses override this method to provide functionality upon an attribute change of value."""
pass
def create_prop(self, key, uselist, **kwargs):
return SmartProperty(self).property(key, uselist)
def create_list(self, obj, key, list_, **kwargs):
return ListElement(obj, key, list_, **kwargs)
def create_callable(self, obj, key, func, uselist, **kwargs):
return CallableProp(self, func, obj, key, uselist, **kwargs)
def get_attribute(self, obj, key, **kwargs):
try:
return self.get_history(obj, key, **kwargs).getattr()
except KeyError:
raise AttributeError(key)
def get_list_attribute(self, obj, key, **kwargs):
return self.get_history(obj, key, **kwargs)
def set_attribute(self, obj, key, value, **kwargs):
if key == 'parent' and value is not None and value.__class__.__name__ != 'Comment':
raise "wha?"
self.get_history(obj, key, **kwargs).setattr(value)
self.value_changed(obj, key, value)
def delete_attribute(self, obj, key, **kwargs):
self.get_history(obj, key, **kwargs).delattr()
self.value_changed(obj, key, None)
def rollback(self, *obj):
for o in obj:
try:
attributes = self.attribute_history(o)
for hist in attributes.values():
hist.rollback()
except KeyError:
pass
def commit(self, *obj):
for o in obj:
try:
attributes = self.attribute_history(o)
for hist in attributes.values():
hist.commit()
except KeyError:
pass
def remove(self, obj):
pass
def create_history(self, obj, key, uselist, callable_=None, **kwargs):
p = self.create_history_container(obj, key, uselist, callable_=callable_, **kwargs)
self.attribute_history(obj)[key] = p
return p
def get_history(self, obj, key, passive=False, **kwargs):
try:
return self.attribute_history(obj)[key].gethistory(passive=passive, **kwargs)
except KeyError, e:
return self.class_managed(obj.__class__)[key](obj, **kwargs).gethistory(passive=passive, **kwargs)
def attribute_history(self, obj):
try:
attr = obj.__dict__['_managed_attributes']
except KeyError:
attr = {}
obj.__dict__['_managed_attributes'] = attr
return attr
def reset_history(self, obj, key):
try:
del self.attribute_history(obj)[key]
except KeyError:
pass
def class_managed(self, class_):
try:
attr = getattr(class_, '_class_managed_attributes')
except AttributeError:
attr = {}
class_._class_managed_attributes = attr
return attr
def create_history_container(self, obj, key, uselist, callable_ = None, **kwargs):
if callable_ is not None:
return self.create_callable(obj, key, callable_, uselist=uselist, **kwargs)
elif not uselist:
return PropHistory(obj, key, **kwargs)
else:
list_ = obj.__dict__.get(key, None)
return self.create_list(obj, key, list_, **kwargs)
def register_attribute(self, class_, key, uselist, callable_=None, **kwargs):
# create a function, that will create this object attribute when its first called
def createprop(obj):
if callable_ is not None:
func = callable_(obj)
else:
func = None
p = self.create_history_container(obj, key, uselist, callable_=func, **kwargs)
self.attribute_history(obj)[key] = p
return p
self.class_managed(class_)[key] = createprop
setattr(class_, key, self.create_prop(key, uselist))
|