some changes to make things pickle compatible...
[pli.git] / pli / tags / tree.py
blobe4a27c04b5951186857b7b3e3d7218130592aae5
1 #=======================================================================
3 __version__ = '''0.0.01'''
4 __sub_version__ = '''20080518211107'''
5 __copyright__ = '''(c) Alex A. Naanou 2003'''
8 #-----------------------------------------------------------------------
10 import pli.tags.generic as generic
11 import pli.tags.tag as tag
12 import pli.tags.path as path
13 import pli.objutils as objutils
14 import pli.pattern.mixin.mapping as mapping
16 ##import oid
19 #-----------------------------------------------------------------------
20 # XXX this needs to get cleaned, partially re-written and possibly
21 # split into several other modules...
22 # XXX write doc and examples...
24 # TODO generate an ID for each object in store!!! (do as a mixin and
25 # possibly not here...)
26 # TODO add support for different format tagchains...
28 #-----------------------------------------------------------------------
29 ##!!! move somewhere and revise !!!##
30 def getoid(obj):
31 '''
32 '''
33 return 'OID_%s' % str(id(obj)).replace('-', 'X')
36 #--------------------------------------------------------------public---
37 ##!!! STUB
38 def public(meth):
39 '''
40 '''
41 return meth
44 #-----------------------------------------------------------------------
45 #-----------------------------------------------------NodeConstructor---
46 # object constructor wraper...
47 # - when created will register and tag itself.
48 # - will proxy to the original constructor and tag the resulting object.
49 class NodeConstructor(object):
50 '''
51 '''
52 # most likely will not need to be changed manualy...
53 ## __object_tags__ = ('instance',)
54 __object_tags__ = ()
56 __tagset__ = None
58 type = 'constructor'
60 # XXX move the defaults out of the code...
61 def __init__(self, name, constructor, *tags):
62 '''
63 '''
64 ## if ('constructor', name) in self.__tagset__:
65 chain = self.__tagset__.tags2chain('constructor', name)
66 if chain in self.__tagset__:
67 raise TypeError, 'constructor id must be unique, id "%s" already exists!' % name
68 self.constructor_name = name
69 self._constructor = constructor
70 self._tags = tags
71 # tag self...
72 ##!!! revise the constructor/name pair format... (use tag groups?)
73 self.__tagset__.tag(self, 'constructor', name, chain, *tags)
74 # XXX would be good to add the "format" support.... (this might be
75 # too generic)
76 def __call__(self, *p, **n):
77 '''
78 create an object.
79 '''
80 res = self.__dict__['_constructor'](*p, **n)
81 self.__tagset__.tag(res, *(self.__object_tags__ + self._tags))
82 return res
83 def __getattr__(self, name):
84 '''
85 '''
86 return getattr(self._constructor, name)
89 #-----------------------------------NodeConstructorWithOIDReturnMixin---
90 ##!!! IMO this functionality should be in the serializer... (if can't
91 ##!!! serialize an object then return it's OID...)
92 class NodeConstructorWithOIDReturnMixin(object):
93 '''
94 '''
95 def __call__(self, *p, **n):
96 '''
97 '''
98 return {'oid': getoid(super(NodeConstructorWithOIDReturnMixin, self).__call__(*p, **n))}
101 #------------------------------------NodeConstructorWithCallbackMixin---
102 class NodeConstructorWithCallbackMixin(object):
105 # defines the name of the inteface method called when the object is
106 # created.
107 # signature:
108 # <method-name>(oid, tagset, tags)
109 __node_constructor_callback_name__ = '__ontreenodecreate__'
111 def __call__(self, *p, **n):
114 res = super(NodeConstructorWithOIDReturnMixin, self).__call__(*p, **n)
115 # call the interface method...
116 if hasattr(res, self.__node_constructor_callback_name__):
117 getattr(res, self.__node_constructor_callback_name__)(getoid(res), self.__tagset__, self.__object_tags__ + self._tags)
118 return res
122 #----------------------------------------------DynamicNodeConstructor---
123 class DynamicNodeConstructor(NodeConstructor):
126 def __init__(self, tagset, name, constructor, common_tags=(), object_tags=()):
129 self.__tagset__ = tagset
130 self.__object_tags__ = object_tags
132 super(DynamicNodeConstructor, self).__init__(name, constructor, *common_tags)
134 def __getstate__(self):
137 return self.__dict__
138 def __setstate__(self, state):
141 self.__dict__.update(state)
143 #---------------------------------DynamicNodeConstructorWithOIDReturn---
144 ##!!! does this need to get reorgonized?
145 class DynamicNodeConstructorWithOIDReturn(NodeConstructorWithOIDReturnMixin, DynamicNodeConstructor):
148 pass
151 #------------------------DynamicNodeConstructorWithOIDReturnNCallback---
152 class DynamicNodeConstructorWithOIDReturnNCallback(NodeConstructorWithCallbackMixin,
153 NodeConstructorWithOIDReturnMixin,
154 DynamicNodeConstructor):
157 pass
160 #-----------------------------------------------------------------------
161 #----------------------------------------------------TagTreePathProxy---
162 # XXX add direct interface...
163 class TagTreePathProxy(path.RecursiveAttrPathProxy):
166 def __getattr__(self, name):
169 if name.startswith('OID_'):
170 return self._getobject(name)
171 ## if ('constructor', name) in self._root:
172 chain = self._root.tags2chain('constructor', name)
173 if chain in self._root:
174 # XXX is this safe??? (constructors should be unique!)
175 ##!!! WARNING: here the select also returns "tag".... check this out!
176 ## return [ o for o in self._root.select(('constructor', name)) if o != generic.TAG_TAG][0]
177 return [ o for o in self._root.select(chain, generic.OBJECT_TAG)\
178 if o != generic.TAG_TAG][0]
179 return super(TagTreePathProxy, self).__getattr__(name)
180 ## __getitem__ = __getattr__
182 # public interface...
183 # in general, this will form the args and call the corresponding
184 # root methods...
185 @public
186 def relatedtags(self, *tags):
189 return self._root.relatedtags(*self._path + tags)
190 @public
191 def chains(self, *tags):
194 return self._root.chains(*self._path + tags)
195 ##!!! OID !!!##
196 # XXX add efficient counting...
197 # XXX split this into two levels... one to return objects and
198 # another to format them into attrs... (???)
199 @public
200 ## def list(self, form=None, to=None, count=None, *attrs):
201 def list(self, *attrs):
203 list the data form the objects in this subtree.
205 res = []
206 # XXX make this iterative...
207 objs = self._root.select(generic.OBJECT_TAG, *self._path)
208 for o in objs:
209 ##!!! the id here is a stub !!!##
210 ## data = {'oid': self._root._getoid(o)}
211 ## data = {'oid': 'OID_%s' % str(id(o)).replace('-', 'X')}
212 data = {'oid': getoid(o)}
213 res += [data]
214 for a in attrs:
215 # skip the oid as we added it already...
216 if a == 'oid':
217 continue
218 # skip attrs that do not exist in the object...
219 if not hasattr(o, a):
220 continue
221 # XXX this is a good spot for ACL check...
222 data[a] = getattr(o, a)
223 return res
225 # object interface...
226 def _getobject(self, oid):
229 ##!!! STUB... do a better direct object by oid select !!!##
230 objs = self._root.select(generic.OBJECT_TAG, *self._path)
231 for o in objs:
232 ##!!! the id here is a stub !!!##
233 ## if ('OID_%s' % str(id(o)).replace('-', 'X')) == oid:
234 if getoid(o) == oid:
235 return o
236 raise TypeError, 'non-existant object id: %s' % oid
238 # shorthand object interface...
239 @objutils.classinstancemethod
240 def view(self, oid, *attrs):
243 return self._getobject(oid).view(*attrs)
244 def update(self, oid, **attrs):
247 return self._getobject(oid).update(**attrs)
250 #----------------------------------------TagTreePathProxyMappingMixin---
251 # XXX make this a tad more efficient...
252 class TagTreePathProxyMappingMixin(mapping.Mapping):
255 def __getitem__(self, name):
258 return self._getobject(name)
259 def __setitem__(self, name, val):
262 raise NotImplementedError
263 def __delitem__(self, name):
266 raise NotImplementedError
267 def __iter__(self):
270 for o in self.list():
271 yield o['oid']
274 #---------------------------------------------TagTreePathProxyMapping---
275 # XXX add exclude support...
276 class TagTreePathProxyMapping(TagTreePathProxyMappingMixin, TagTreePathProxy):
279 pass
282 #--------------------------------------------------------TagTreeMixin---
283 ##!!!! not yet very picklable...
284 class TagTreeMixin(tag.AbstractTagSet):
287 # this is needed here as the attr proxy try and get it othewise...
288 # ...another way to go is add an exception to the .__getattr__
289 # method, explicitly raising attrerror when this is not here...
290 ## __stored_set_constructor__ = None
292 __node_path_proxy__ = TagTreePathProxyMapping
294 def __getattr__(self, name):
297 # support pickle...
298 ##!!! automate this... (might be a good idea to put this into path.py)
299 ## if name in (
300 ## '__getstate__',
301 ## '__setstate__',
302 ## '__reduce__',
303 ## '__reduce_ex__',
304 ## '__slots__',
305 ## '__getnewargs__'):
306 if name.startswith('__') and name.endswith('__'):
307 return super(TagTreeMixin, self).__getattr__(name)
308 return getattr(self.__node_path_proxy__(self, ()), name)
310 ## ##!!! for type-checking to work need to split the tag method into two:
311 ## ##!!! one for internal use and one "public"...
312 ## @public
313 ## def tag(self, obj, *tags):
314 ## '''
315 ## '''
316 ## for t in tags:
317 ## if type(t) not in (str, unicode):
318 ## raise TypeError, 'tag type must be str, got %s.' % type(t)
319 ## return super(TagTreeMixin, self).tag(obj, *tags)
320 @public
321 def relatedtags(self, *tags):
324 # skip "special" constructor IDs...
325 return set( t for t in super(TagTreeMixin, self).relatedtags(*tags)
326 if type(t) != tuple )
329 #-------------------------------------------------------------TagTree---
330 class TagTree(TagTreeMixin, tag.TagSet):
333 __stored_set_constructor__ = set
335 ## def __getstate__(self):
336 ## '''
337 ## '''
338 ## return 1
339 ## def __setstate__(self, state):
340 ## '''
341 ## '''
342 ## pass
346 #-----------------------------------------------------------------------
347 # XXX this can be considered app-level code... most likely should be
348 # elsware...
349 #--------------------------------------------------BaseTagTreeHandler---
350 class BaseTagTreeHandler(object):
352 this provides administrative tree handling, without affecting the tree itself.
354 __tree_constructor__ = None
355 __node_constructor_wrapper__ = None
357 __constructor_tags__ = ()
358 __instance_tags__ = ()
360 tree = None
362 # XXX might be good to be able to thread some args to the tree
363 # constructor...
364 def __init__(self, constructor_tags=(), instance_tags=()):
367 self.__constructor_tags__ += constructor_tags
368 self.__instance_tags__ += instance_tags
370 self.tree = self.__tree_constructor__()
371 def constructor(self, name, constructor, constructor_tags=(), instance_tags=(), ignore_default_constructor_tags=False, ignore_default_instance_tags=False):
373 create a custom constructor.
375 # construct tag lists...
376 if ignore_default_constructor_tags:
377 c_tags = constructor_tags
378 else:
379 c_tags = self.__constructor_tags__ + constructor_tags
380 if ignore_default_instance_tags:
381 i_tags = instance_tags
382 else:
383 i_tags = self.__instance_tags__ + instance_tags
384 # do the call...
385 return self.__node_constructor_wrapper__(self.tree, name, constructor,
386 common_tags=c_tags,
387 object_tags=i_tags)
390 #------------------------------------------------------TagTreeHandler---
391 class TagTreeHandler(BaseTagTreeHandler):
394 __tree_constructor__ = TagTree
395 ## __node_constructor_wrapper__ = DynamicNodeConstructor
396 ## __node_constructor_wrapper__ = DynamicNodeConstructorWithOIDReturn
397 __node_constructor_wrapper__ = DynamicNodeConstructorWithOIDReturnNCallback
399 # XXX this is currently not needed as the NodeConstructor class
400 # takes care of this internally.... (should be customisable!)
401 ## __constructor_tags__ = ('constructor',)
402 __instance_tags__ = ('instance',)
404 # XXX this is currently not needed as the NodeConstructor class
405 # takes care of this internally.... (should be customisable!)
406 ## def constructor(self, name, constructor, constructor_tags=(), instance_tags=()):
407 ## '''
408 ## '''
409 ## return super(TagTreeHandler, self).constructor(
410 ## name,
411 ## constructor,
412 ## (name, ('constructor', name)) + constructor_tags,
413 ## instance_tags)
417 #-----------------------------------------------------------------------
418 if __name__ == '__main__':
420 handler = TagTreeHandler()
421 tree = handler.tree
423 def constructor(name, constructor, *tags):
424 return handler.constructor(name, constructor, tags)
427 class A(object):
428 attr = 123
429 class B(object):
430 pass
432 print 'creating constructors...'
433 print constructor('A', A, 'some_tag')
434 print constructor('B', B, 'some_other_tag')
435 print
437 print tree.constructor.list()
438 print
439 print tree.some_tag.constructor.list()
440 print
441 print tree.some_tag.relatedtags()
442 print
443 print tree.A
444 print tree.A()
445 print tree.some_tag.A()
446 print tree.some_tag.constructor.A()
447 print tree.B()
448 print tree.some_other_tag.constructor.B()
449 print
450 print tree.instance.list('attr', '__class__')
451 print
452 print tree.some_tag.instance.list()
453 print
454 print tree.some_other_tag.list()
455 print
456 print tree.relatedtags()
457 print tree.some_other_tag.relatedtags()
459 print tree.addtags('xxx', 'yyy')
460 print tree.xxx.keys()
462 AA = constructor('X', A, 'fff:ggg')
463 tree.X()
465 print tree['fff:ggg']
466 print tree['fff']
468 print
469 print
473 ## ##!!! pickle does not seem to work with recursive references... (2.5.1-specific?)
474 ## import pickle
476 ## class X(object): pass
478 ## x = X()
480 ## d = {'x':x}
481 ## x.d = d
482 ## print d
483 ## print pickle.dumps(d)
485 import cPickle as pickle
487 s = pickle.dumps(tree)
489 ss = pickle.dumps(tree.some_tag)
491 print AA
492 sss = pickle.dumps(AA)
496 #=======================================================================
497 # vim:set ts=4 sw=4 nowrap :