...
[pli.git] / pli / logictypes.py
blob34e230867c508b41ff0d9a6a8d5f6fddfa7d6a47
1 #=======================================================================
3 __version__ = '''0.1.21'''
4 __sub_version__ = '''20120704184439'''
5 __copyright__ = '''(c) Alex A. Naanou 2003'''
7 __doc__ = '''\
8 this module defines a number of utilities and objects to assist advanced
9 usage of standard python types.
10 '''
12 #-----------------------------------------------------------------------
14 import copy
16 import pli.pattern.mixin.mapping as mapping
17 import pli.pattern.proxy.utils as proxyutils
18 import pli.objutils as objutils
21 #-----------------------------------------------------------------------
22 #-------------------------------------------------------------Pattern---
23 class Pattern(object):
24 '''
25 '''
26 pass
29 #-------------------------------------------------------------Compare---
30 class Compare(Pattern):
31 '''
32 '''
33 original = True
35 def __init__(self, eq, name=None):
36 self._eq = eq
37 if name == None:
38 self._name = self.__class__.__name__
39 else:
40 self._name = name
41 def __call__(self):
42 '''
43 create a copy of self.
44 '''
45 res = copy.copy(self)
46 res.original = False
47 return res
48 def __repr__(self):
49 if self.original is True:
50 return self._name
51 return '<%s object %s>' % (self._name, hash(self))
52 # comparison methods...
53 def __cmp__(self, other):
54 return self._eq
55 def __eq__(self, other):
56 return self._eq == 0
57 def __ne__(self, other):
58 return self._eq != 0
59 def __gt__(self, other):
60 return self._eq > 0
61 def __ge__(self, other):
62 return self._eq >= 0
63 def __lt__(self, other):
64 return self._eq < 0
65 def __le__(self, other):
66 return self._eq <= 0
68 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
69 # this will compare to any value as equel (almost oposite to None)
70 Any = ANY = Compare(0, 'ANY')
72 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
73 # this is bigger than any value...
74 MAXIMUM = Compare(1, 'MAXIMUM')
76 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
77 # this is smaller than any value...
78 MINIMUM = Compare(-1, 'MINIMUM')
81 #-----------------------------------------------------------------AND---
82 class AND(Pattern):
83 '''
84 AND(A, B[, ..]) == X iff X == A and X == B [and ..]
85 '''
86 def __init__(self, A, B, *patterns):
87 '''
88 '''
89 self.patterns = (A, B) + patterns
90 def __repr__(self):
91 '''
92 '''
93 return 'AND(%s)' % ', '.join(repr(p) for p in self.patterns)
94 # XXX revise comparison order...
95 def __eq__(self, other):
96 return False not in [ other == p for p in self.patterns ]
97 def __ne__(self, other):
98 for p in self.patterns:
99 if other != p:
100 return True
101 return False
104 #------------------------------------------------------------------OR---
105 class OR(Pattern):
107 OR(A, B[, ..]) == X iff X == A or X == B [and ..]
109 def __init__(self, A, B, *patterns):
112 self.patterns = (A, B) + patterns
113 def __repr__(self):
116 return 'OR(%s)' % ', '.join(repr(p) for p in self.patterns)
117 # XXX revise comparison order...
118 def __eq__(self, other):
119 for p in self.patterns:
120 if other == p:
121 return True
122 return False
123 def __ne__(self, other):
124 return False not in [ other != p for p in self.patterns ]
127 #-----------------------------------------------------------------NOT---
128 class NOT(Pattern):
130 NOT(A) == X iff A != X
132 def __init__(self, obj):
133 self.obj = obj
134 def __repr__(self):
137 return 'NOT(%r)' % self.obj
138 def __eq__(self, other):
139 return not (self.obj == other)
140 def __ne__(self, other):
141 return not (self.obj != other)
144 #------------------------------------------------------------------IN---
145 # NOTE: this looks odd :)
146 class IN(Pattern):
148 IN(A) == X iff A in X
149 IN(A, B) == X iff B == X and A in X
151 def __init__(self, obj, container=ANY):
152 self.obj = obj
153 self.container = container
154 def __repr__(self):
157 return 'IN(%r, %r)' % (self.obj, self.container)
158 def __eq__(self, other):
159 return self.container == other and self.obj in other
160 def __ne__(self, other):
161 return self.container != other or self.obj not in other
164 #------------------------------------------------------------------AT---
165 class AT(Pattern):
167 AT(A) == X iff A in X and X[ANY] == A
168 AT(A, B) == X iff A in X and X[B] == A
170 NOTE: this works as good as python's containers accept patterns.
171 this mostly amounts to staying clear of using patterns as
172 indexes, thus the first case might not work correctly.
174 def __init__(self, obj, position=ANY):
175 self.obj = obj
176 self.position = position
177 def __repr__(self):
178 return 'AT(%r, %r)' % (self.obj, self.position)
179 def __eq__(self, other):
180 try:
181 if other == SEQUENCE:
182 return self.obj in other \
183 and other[self.position] == self.obj
184 else:
185 # mappings...
186 return self.obj in other.values() \
187 and other[self.position] == self.obj
188 except (KeyError, IndexError):
189 return False
190 def __ne__(self, other):
191 try:
192 if other == SEQUENCE:
193 return self.obj not in other \
194 or other[self.position] != self.obj
195 else:
196 return self.obj not in other.values() \
197 or other[self.position] != self.obj
198 except (KeyError, IndexError):
199 return False
203 #-----------------------------------------------------------------------
204 # TODO rename...
205 # TODO add chaining support...
206 # TODO add shadowing support... (via NOTHING, afect chained values)
207 class ErxCONTAINER(Pattern, mapping.Mapping):
210 has the folowing notions:
211 - length, if content is sizable or has length
212 - content/containment
213 - bounds
215 ##!!! we need to pass data arround!
216 def __init__(self, data=()):
217 ##!!! we need to pass data arround!
218 ## objutils.termsuper(ErxCONTAINER, self).__init__(data)
219 objutils.termsuper(ErxCONTAINER, self).__init__()
220 self._data = data
221 # mapping-like interface...
222 # NOTE: this is mainly needed to bypass pythons dict optimizations
223 # thus enabling patterns...
224 def __getitem__(self, pattern):
227 # XXX might be good to make this check the obvious first...
228 for e in self:
229 if pattern == e:
230 return e
231 # XXX should this fail like this??
232 raise KeyError, pattern
233 def __setitem__(self, pattern, value):
236 del self[pattern]
237 self._data += (value,)
238 def __delitem__(self, pattern):
241 res = list(self)
242 while pattern in res:
243 res.remove(pattern)
244 self._data = tuple(res)
245 ##!!! need raw iter, to iterate and detect content items...
246 def __iter__(self):
249 for e in self._data:
250 if isinstance(e, CONTENT):
251 for ee in e:
252 yield ee
253 else:
254 yield e
255 # pattern interface...
256 def __eq__(self, other):
259 # NOTE: this will go through content items via .__iter__
260 for e in other:
261 if e not in self:
262 return False
263 return len(self) == len(other)
264 def __ne__(self, other):
267 # XXX is there a better way to do this?
268 return not (self == other)
271 C = ErxCONTAINER
274 ##!!! should this be a view or a container?
275 class ORDERED(ErxCONTAINER):
277 takes a container as argument and returns a container that also considers
278 the nothion of order.
280 def __eq__(self, other):
283 if len(self) == len(other):
284 return tuple(self) == tuple(other)
285 return False
286 def __ne__(self, other):
289 pass
292 ##!!! should this be a view or a container?
293 # XXX this should be like OR -- match single elements as well as
294 # sub-sequences... i.e. '==' and 'in' should be equivalent but
295 # strictly prioretized...
296 class CONTENT(ErxCONTAINER):
298 like a container but matches a part of a container.
303 # views...
304 class VIEW(ErxCONTAINER):
307 def __init__(self, pattern, container):
310 objutils.termsuper(VIEW, self).__init__()
311 self.pattern = pattern
312 self.container = container
314 def __getitem__(self, pattern):
317 try:
318 return self.container[AND(pattern, self.pattern)]
319 except KeyError:
320 raise KeyError, pattern
321 def __setitem__(self, pattern, value):
324 try:
325 self.container[AND(pattern, self.pattern)] = value
326 except KeyError:
327 raise KeyError, pattern
328 def __delitem__(self, pattern):
331 del self.container[AND(pattern, self.pattern)]
332 def __iter__(self):
335 pattern = self.pattern
336 for e in self.container:
337 if pattern == e:
338 yield e
341 # XXX this should match the value or item pattern...
342 # XXX need a reliable way to position this at next/prev values... this
343 # must be as independent of container manipulation as possible...
344 # XXX need an efficient way to directly position within a container...
345 # the "visited bag" approach is BAD for very large containers!
346 # one way to do this is to remember a "current" element. but this
347 # will break if it is deleted or modified...
348 class ITEM(Pattern):
351 def __init__(self, pattern, container, visited=()):
354 objutils.termsuper(ITEM, self).__init__()
355 self.pattern = pattern
356 self.container = container
357 self.visited = visited
359 @property
360 # XXX may be a good idea to combine value with assign a-la
361 # jQuery... actually, might even add delete into the mix -- we
362 # do have a NOTHING pattern don't we?
363 # ...still need some more thought.
364 def value(self):
367 visited = self.visited
368 if len(visited) == 0:
369 return self.container[self.pattern]
370 elif len(visited) == 1:
371 return self.container[AND(NOT(self.visited[0]), self.pattern)]
372 return self.container[AND(NOT(OR(*self.visited)), self.pattern)]
373 ##!!! what should happen of we changed the value or updated the container
374 ##!!! so as this does not have a value anymore?
375 def assign(self, value):
378 try:
379 self.container[self.value] = value
380 except KeyError:
381 # this means we are not looking at a meaningfull
382 # position...
383 # add a new element.
384 ##!!! is this correct???
385 self.container[NOTHING] = value
386 return self
387 ##!!! revise...
388 def delete(self):
391 try:
392 del self.container[self.value]
393 except KeyError:
394 pass
395 return self
397 @property
398 def next(self):
401 visited = self.visited + (self.value,)
402 res = self.__class__(self.pattern, self.container, visited)
403 # this is here to break things and to prevent ust to iterate
404 # beyond where we started...
405 res.value
406 return res
407 @property
408 def prev(self):
411 visited = self.visited
412 # we can't iterate over the beginning...
413 if len(visited) == 0:
414 raise KeyError, self.pattern
415 pattern = self.pattern
416 container = self.container
417 for v in visited[::-1]:
418 if v not in container:
419 # remove the non-existing elements...
420 visited = visited[:-1]
421 # this is to handle the case of we are positioned
422 # somewhere in a chain and all the items we saw before
423 # do not exist any longer...
424 if len(visited) == 0:
425 raise KeyError, self.pattern
426 continue
427 res = self.__class__(pattern, container, visited[:-1])
428 # this is here to break things and to prevent ust to iterate
429 # beyond where we started...
430 res.value
431 return res
435 # variable length contents...
436 class OF(CONTENT):
438 OF(A, N) == X iff A occurs N times in X
440 Pythonism for N of A in Eryx.
442 def __init__(self, obj, count):
443 super(OF, self).__init__()
444 self.obj, self.count = obj, count
449 #-----------------------------------------------------------------------
450 # this is to define basic pattern combination mathematics.
451 # Ex:
452 # Pab = Pa | Pb -- Pab is a pattern that will match either Pa
453 # or Pb...
455 # Operations:
459 # rep
460 # not
463 ##class Pattern(object):
464 ## '''
465 ## '''
466 ## # cmp
467 ## def __eq__(self, other):
468 ## '''
469 ## '''
470 ## pass
471 ## def __ne__(self, other):
472 ## '''
473 ## '''
474 ## pass
475 ## def __gt__(self, other):
476 ## '''
477 ## '''
478 ## pass
479 ## def __ge__(self, other):
480 ## '''
481 ## '''
482 ## pass
483 ## def __lt__(self, other):
484 ## '''
485 ## '''
486 ## pass
487 ## def __le__(self, other):
488 ## '''
489 ## '''
490 ## pass
491 ## # op
492 ## ##!!!
495 #--------------------------------------------------------------oftype---
496 class OfType(Pattern):
498 this will create an object that can be used as a predicate to test type,
499 and it will copare True to objects of that type.
501 def __init__(self, *types, **n):
504 self._types = types
505 if 'doc' in n:
506 self.__doc__ = n['doc']
507 def __call__(self, other):
509 test if the the other object object is of type.
511 return isinstance(other, self._types) is True
512 __eq__ = __call__
513 def __ne__(self, other):
514 return not self(other)
515 ## return not isinstance(other, self._types) is True
516 def __gt__(self, other):
517 return False
518 def __ge__(self, other):
519 return True
520 def __lt__(self, other):
521 return False
522 def __le__(self, other):
523 return True
525 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
526 oftype = OfType
529 #-------------------------------------------------OfTypeWithPredicate---
530 class OfTypeWithPredicate(OfType):
533 def __call__(self, other):
536 if self.__predicate__(other):
537 return super(OfTypeWithPredicate, self).__call__(other)
538 return False
539 __eq__ = __call__
540 # NOTE: this is intended to be oveloaded... (by default it will
541 # break the object...)
542 def __predicate__(self, other):
544 this will check the object.
546 must return True or False.
548 raise NotImplementedError
551 #----------------------------------------------OfTypeWithArgPredicate---
552 # WARNING: this might not be compatible with CPythons pickle...
553 class OfTypeWithArgPredicate(OfTypeWithPredicate):
556 def __init__(self, *p, **n):
559 super(OfTypeWithArgPredicate, self).__init__(*p, **n)
560 if 'predicate' in n:
561 self.__predicate__ = n['predicate']
562 else:
563 raise TypeError, 'a predicate must be given.'
565 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
567 ofptype = OfTypeWithArgPredicate
570 #-----------------------------------------------------------------------
571 # simple type predicates...
572 INT = isint = oftype(int)
573 FLOAT = isfloat = oftype(float)
574 COMPLEX = iscomplex = oftype(complex)
576 STR = isstr = oftype(str)
577 UNICODE = isunicode = oftype(unicode)
579 LIST = islist = oftype(list)
580 TUPLE = istuple = oftype(tuple)
581 DICT = isdict = oftype(dict)
582 SET = isset = oftype(set)
584 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
585 # other odd predicates :)
586 ODD = isodd = ofptype(int, long, predicate=lambda o: o%2 != 0)
587 EVEN = iseven = ofptype(int, long, predicate=lambda o: o%2 == 0)
590 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
591 # general type groups predicates...
592 NUMBER = isnumber = oftype(int, float, complex)
594 STRING = isstring = oftype(str, unicode)
596 SEQUENCE = issequence = oftype(list, tuple)
597 CONTAINER = iscontainer = oftype(list, tuple, dict, set)
599 ITERABLETYPE = isiterabletype = oftype(str, unicode, list, tuple, dict, set)
602 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
603 IDENT = isident = ofptype(str, unicode, predicate=lambda o: len(o) > 0 \
604 and False not in [ c.isalnum() \
605 for c in o.split('_') ] \
606 and o[0].isalpha())
609 ###-----------------------------------------------------------------------
610 ##class isLike(Pattern):
611 ## '''
612 ## '''
613 ## def __init__(self, *types, **n):
614 ## '''
615 ## '''
616 ## self._types = types
617 ## if 'doc' in n:
618 ## self.__doc__ = n['doc']
619 ## def __call__(self, other):
620 ## '''
621 ## test if the the other object object is of type.
622 ## '''
623 ## return isinstance(other, self._types) is True
624 ## __eq__ = __call__
625 ## def __ne__(self, other):
626 ## return not isinstance(other, self._types) is True
627 ## def __gt__(self, other):
628 ## return False
629 ## def __ge__(self, other):
630 ## return True
631 ## def __lt__(self, other):
632 ## return False
633 ## def __le__(self, other):
634 ## return True
638 #-----------------------------------------------------------------------
639 #-------------------------------------------------------dictcopyunite---
640 def dictcopyunite(*members):
643 res = {}
644 for m in members:
645 res.update(m)
646 return res
649 #---------------------------------------------------AbstractDictChainMixin---
650 # TODO split this into several mix-ins....
651 # NOTE: this was not designed to be used directly...
652 class AbstractDictChainMixin(mapping.Mapping):
655 # mapping interface...
656 def __getitem__(self, name):
659 for m in self.__iterchainmembers__():
660 if name in m:
661 return m[name]
662 raise KeyError, 'key "%s" is not present in any of the members.' % name
663 def __setitem__(self, name, value):
666 # find source...
667 # set
668 raise TypeError, 'can\'t add values to an AbstractDictChainMixin object.'
669 def __delitem__(self, name):
672 raise TypeError, 'can\'t delete values from an AbstractDictChainMixin object.'
673 def __contains__(self, name):
676 for m in self.__iterchainmembers__():
677 if name in m:
678 return True
679 return False
680 def __iter__(self):
683 seen = []
684 for m in self.__iterchainmembers__():
685 for n in m:
686 if n not in seen:
687 seen += [n]
688 yield n
689 # AbstractDictChainMixin specific extension interface...
690 ## def __iterchainmembers__(self):
691 ## '''
692 ## this will yield dicts, elements of the chain.
693 ## '''
694 ## raise NotImplementedError
695 # AbstractDictChainMixin specific methods...
696 def todict(self):
698 this will return a dict copy of the DictUnion object.
700 return dict(self.items())
703 #-----------------------------------------------------------DictUnion---
704 # XXX might be good to combine this and DictChain... (this will be
705 # quite easy using the itermembers instead of self._members)
706 class DictUnion(AbstractDictChainMixin):
708 this is a dict like object, that acts as a union of its members but
709 without modifieng its members in any way.
711 this is similar to a sequential update of all members, but retains
712 the container information for each item, and does not have the
713 overhead of creating a new resulting dict.
715 NOTE: because of the nature of dicts, the later added members (unite)
716 have higher priority than the former.
717 NOTE: the members added in one call (be it __init__, unite or tailunite)
718 have descending priority -- first highest last lowest.
719 NOTE: due to ints nature this object is *live*, e.g. it emidiatly
720 reflects all modifications to its members as they are modified.
722 _members = ()
724 def __init__(self, *members):
727 members = list(members)
728 members.reverse()
729 self._members = tuple(members)
730 # AbstractDictChainMixin specific extension interface...
731 def __iterchainmembers__(self):
734 for m in self._members:
735 yield m
736 # the DictUnion specific interface...
737 # XXX should these depend on __iterchainmembers__ or directly on
738 # _members????
739 def unite(self, *others):
741 add members to the union object.
743 NOTE: this method will add members to the top of the pririty
744 stack.
745 NOTE: the elements are added in sequence, thus, the later elements
746 have higher priority.
748 others = list(others)
749 others.reverse()
750 self._members = tuple(others) + self._members
751 def tailunite(self, *others):
753 this is the same as unite but adds low priority members (to the
754 botom of the priority stack).
756 NOTE: the elements are added in sequence, thus, the later elements
757 have lower priority.
759 others = list(others)
760 self._members = self._members + tuple(others)
761 def memberindex(self, obj):
764 return list(self._members).index(obj)
765 def removemember(self, obj):
768 if obj in self._members:
769 self._members = tuple(list(self._members).remove(obj))
770 return
771 raise TypeError, '%s does not contain %s as a member.' % (self, obj)
772 def clearmembers(self):
775 ## del self._members[:]
776 self._members = ()
777 def members(self):
780 return self._members
781 def popmember(self, index=0):
784 NOTE: this by default will remove the highest priority member.
786 m = list(self._members)
787 res = m.pop(index)
788 self._members = tuple(m)
789 return res
790 itermembers = __iterchainmembers__
791 def getcontainerof(self, name):
794 for m in self._members:
795 if name in m:
796 return m
797 raise KeyError, '%s does not contain "%s"' % (self, name)
798 def getallcontainersof(self, name):
801 res = []
802 for m in self._members:
803 if name in m:
804 res += [m]
805 if res == []:
806 raise KeyError, '%s does not contain "%s"' % (self, name)
807 return res
810 #---------------------------------------------------WritableDictUnion---
811 # modes:
812 # get the last occurance...
813 GET_LOCAL_LAST = 0
814 # get the first occurance...
815 GET_LOCAL_FIRST = 1
817 # if key is not contained in union, add it to the last member...
818 WRITE_NEW_TO_LAST = 0
819 # if key is not contained in union, add it to the first member...
820 WRITE_NEW_TO_FIRST = 2
821 # if the key is present in the union more than once, override the last
822 # occurance...
823 WRITE_TO_LAST_OWNER = 0
824 # if the key is present in the union more than once, override the first
825 # occurance...
826 WRITE_TO_FIRST_OWNER = 4
827 # write to local (temporary) dict...
828 # NOTE: if this is set, other write opts will be ignored...
829 WRITE_LOCAL = 8
830 # disable write...
831 WRITE_DISABLED = 16
833 # delete the last occurance of key...
834 DELETE_LAST = 0
835 # delete the first occurance of key...
836 DELETE_FIRST = 32
837 # enable local key delete...
838 DELETE_LOCAL = 64
839 # disable deletion of anything but local keys...
840 DELETE_LOCAL_ONLY = 128
841 # disable delete...
842 DELETE_DISABLED = 256
844 class WritableDictUnion(DictUnion):
847 __modify_props__ = GET_LOCAL_FIRST \
848 | WRITE_NEW_TO_FIRST \
849 | WRITE_TO_FIRST_OWNER \
850 | DELETE_FIRST \
851 | DELETE_LOCAL
853 def __init__(self, *members):
856 super(WritableDictUnion, self).__init__(*members)
857 self._locals = {}
858 def __getitem__(self, name):
861 props = getattr(self, '__modify_props__', 0)
862 if props & GET_LOCAL_FIRST:
863 if name in self._locals:
864 return self._locals[name]
865 else:
866 return super(WritableDictUnion, self).__getitem__(name)
867 try:
868 return super(WritableDictUnion, self).__getitem__(name)
869 except KeyError:
870 if name in self._locals:
871 return self._locals[name]
872 raise KeyError, name
873 def __setitem__(self, name, value):
876 props = getattr(self, '__modify_props__', 0)
877 if props & WRITE_DISABLED:
878 raise TypeError, 'can\'t add values to a dict union object (writing is disabled).'
879 elif props & WRITE_LOCAL:
880 self._locals[name] = value
881 else:
882 try:
883 if props & WRITE_TO_FIRST_OWNER:
884 self.getallcontainersof(name)[0][name] = value
885 else:
886 self.getallcontainersof(name)[-1][name] = value
887 except KeyError:
888 ##!!! problem if there are no memebers... raise an apropriate error...
889 if props & WRITE_NEW_TO_FIRST:
890 self.members()[0][name] = value
891 else:
892 self.members()[-1][name] = value
893 def __contains__(self, name):
896 if name in self._locals or super(WritableDictUnion, self).__contains__(name):
897 return True
898 return False
899 def __delitem__(self, name):
902 props = getattr(self, '__modify_props__', 0)
903 if props & DELETE_DISABLED:
904 raise TypeError, 'can\'t delete items (deletion is disabled).'
905 if props & DELETE_LOCAL and name in self._locals:
906 del self._locals[name]
907 elif not props & DELETE_LOCAL_ONLY:
908 if props & DELETE_FIRST:
909 del self.getallcontainersof(name)[0][name]
910 else:
911 del self.getallcontainersof(name)[-1][name]
912 else:
913 raise TypeError, 'can\'t delete from contained dicts.'
914 def __iter__(self):
917 seen = []
918 props = getattr(self, '__modify_props__', 0)
919 if props & GET_LOCAL_FIRST:
920 for k in self._locals:
921 yield k
922 seen += [k]
923 for k in super(WritableDictUnion, self).__iter__():
924 if k not in seen:
925 yield k
926 seen += [k]
927 if not props & GET_LOCAL_FIRST:
928 for k in self._locals:
929 if k not in seen:
930 yield k
934 #-------------------------------------------------------DictTypeUnion---
935 # WARNING: this is not done!
936 class DictTypeUnion(DictUnion, dict):
938 this is a diriviation from dict that can contain oun data.
940 ##!!!
941 pass
944 #---------------------------------------------------BasicMappingChain---
945 class BasicMappingChain(AbstractDictChainMixin, mapping.Mapping):
948 NOTE: this class was designed as a basic base class (atleast the
949 __iterchainmembers__ method should be overloaded).
950 NOTE: do not forget to call the original __iterchainmembers__.
951 NOTE: this, if used as-is will not differ from a dict.
953 _dict_data = None
955 def __init__(self, *p, **n):
958 self._dict_data = {}
959 super(BasicMappingChain, self).__init__(*p, **n)
960 proxyutils.proxymethods((
961 '__setitem__',
962 '__delitem__',
963 'clear',
964 ), '_dict_data')
965 def __iterchainmembers__(self):
968 yield self._dict_data
970 def localkeys(self):
972 return list of local to the current node keys.
974 return self._dict_data.keys()
977 #--------------------------------------------------------MappingChain---
978 class MappingChain(BasicMappingChain):
980 this is a basic mapping chain element.
982 when a key can not be found in the local data then the request is
983 forwarded to the .chain_next attribute.
985 NOTE: .chain_next can be None, then no other forwarding is done.
986 NOTE: editing is possible only to the local data.
988 chain_next = None
990 def __iterchainmembers__(self):
993 for v in super(MappingChain, self).__iterchainmembers__():
994 yield v
995 if self.chain_next != None:
996 yield self.chain_next
999 #-----------------------------------------------MappingChainLiveMixin---
1000 class MappingChainLiveMixin(object):
1003 _chain_attr_name = None
1004 _chain_next_obj = None
1006 chain_next = property(
1007 fget=lambda self: getattr(self.__chain_next_obj__, self.__chain_attr_name__),
1008 fset=lambda self, val: (setattr(self, '__chain_next_obj__', val[0]),
1009 setattr(self, '__chain_attr_name__', val[1])))
1012 #-----------------------------------------------------------DictChain---
1013 class DictChain(MappingChain, mapping.DictLike):
1015 same as MappingChain but more dict like.
1017 see docs for:
1018 pli.logictypes.MappingChain
1019 pli.pattern.mixin.mapping.DictLike
1021 pass
1024 #-------------------------------------------------------LiveDictChain---
1025 class LiveDictChain(MappingChainLiveMixin, DictChain):
1028 pass
1031 #---------------------------------------------------------dictchainto---
1032 def dictchainto(dct):
1034 create a dictchain over a mapping.
1036 helper/factory function.
1038 d = DictChain()
1039 d.chain_next = dct
1040 return d
1043 #-----------------------------------------------------livedictchainto---
1044 def livedictchainto(obj, attr):
1046 create a live dictchain over a mapping.
1048 helper/factory function.
1050 d = LiveDictChain()
1051 d.chain_next = (obj, attr)
1052 return d
1056 #-----------------------------------------------------------------------
1057 #---------------------------------------------------------ObjectUnion---
1058 # TODO rename....
1059 # TODO split to various mix-ins...
1060 ##class BasicObjectUnion(object):
1061 class ObjectUnion(object):
1063 this represents the union of the attr of several objects.
1065 def __init__(self, *members):
1068 members = list(members)
1069 members.reverse()
1070 self._members = tuple(members)
1071 ##!!!
1072 def __getattr__(self, name):
1075 for o in self._members:
1076 if hasattr(o, name):
1077 return getattr(o, name)
1078 ## # Q: which is faster, this or the "if hasattr..."???
1079 ## try:
1080 ## return getattr(o, name)
1081 ## except AttributeError:
1082 ## pass
1083 raise AttributeError, name
1084 ## def __setattr__(self, name, value):
1085 ## '''
1086 ## '''
1087 ## raise TypeError, 'ca\'t write attributes of a ObjectUnion object.'
1088 ## def __delattr__(self, name, value):
1089 ## '''
1090 ## '''
1091 ## pass
1092 def __hasattr__(self, name):
1095 for o in self._members:
1096 if hasattr(o, name):
1097 return True
1098 return False
1099 # interface methods...
1100 ##!!!
1103 #=======================================================================
1104 if __name__ == '__main__':
1105 d0 = {'a':1, 'b':2}
1106 d1 = {'c':'!!!!!', 'b':'x'}
1108 D = WritableDictUnion(d0, d1)
1110 D['b'] = '*****'
1112 print D['b']
1114 print D.todict()
1115 print d0, d1
1117 print 'a' in D
1119 print isodd(3), isodd(6)
1121 print isident('b0_c'), isident('aaa'), isident(''), isident('321_4')
1123 # check the dict chain...
1124 d = DictChain(a=1, b=2, c=3)
1125 print d.todict()
1126 d.chain_next = DictChain(c=4, d=5, e=6)
1127 print d.todict()
1128 d.chain_next.chain_next = dict(e=7, f=8, g=9)
1129 print d.todict()
1132 class X(object):
1133 xxx = dict(a=1, b=2)
1135 x = X()
1137 ld = livedictchainto(x, 'xxx')
1138 ld['b'] = 3
1139 ld['c'] = 4
1141 print ld.keys()
1143 x.xxx = {}
1145 print ld.keys()
1147 ld['aaa'] = 'bbb'
1149 print ld.keys()
1151 del ld['aaa']
1153 print ld.keys()
1155 print ANY, ANY()
1157 print AND((1, ANY), (ANY, 2))
1158 print AND((1, ANY), (ANY, 2)) == (1, 2)
1159 print AND((1, ANY), (ANY, 2)) != (ANY, 9)
1160 print OR((1, ANY), (ANY, 2)) == (1, 8)
1161 print OR((1, ANY), (ANY, 2)) != (7, 8)
1163 print AT(1, 10) == range(100)
1164 print AT(10, 10) == range(100)
1167 e = C((1, 2, 3, 'a', 'b', 'c', CONTENT(C((1.1, 2.2, 3.3)))))
1169 print e
1171 print e[ANY]
1172 print e[STRING]
1173 print e[3]
1174 print e[FLOAT]
1175 print len(e)
1176 ##!!! there should be a way to access content items...
1177 ## print e[CONTENT]
1179 print e == (1, 2, 3, 'a', 'c', 1.1, 2.2, 3.3, 'b')
1180 print ORDERED(e) == (1, 2, 3, 'a', 'c', 1.1, 2.2, 3.3, 'b')
1181 print ORDERED(e) == (1, 2, 3, 'a', 'b', 'c', 1.1, 2.2, 3.3)
1182 print ORDERED(CONTENT(e)) == (1, 2, 3, 'a', 'b', 'c', 1.1, 2.2, 3.3)
1185 v = VIEW(NUMBER, e)
1186 print len(v)
1187 v[INT] = 1
1188 print len(v)
1190 print list(v)
1192 i = ITEM(NUMBER, e)
1193 print i.value
1194 print i.next.value
1195 print i.next.next.value
1196 print i.next.next.next.value
1197 print i.next.next.next.assign(2)
1198 print i.next.next.next.value
1199 # this will assign a value that does not match the pattern of
1200 # either the item or the view. thus, it will not appear in either
1201 # of them.
1202 # ...assigning to an item an that item still not having a value may
1203 # be counter intuitive.
1204 # another similar case would be assigning a value that we already
1205 # passed (this depends on the scheme of the positioning algorithm
1206 # of the item...)
1207 ##!!! this makes the item inconsistent...
1208 ##!!! ...asigning t a value incompatible to the pattern will make the
1209 ##!!! item object inconsistent -- heed to handle this in some obvious way
1210 ## print i.next.next.next.assign('aaa')
1211 ## print i.next.next.next
1212 print list(v)
1213 print list(e)
1215 print i.value
1216 print i.next.value
1217 print i.next.next.value
1218 print i.next.next.next.value
1219 print i.next.next.next.prev.value
1220 print i.next.next.next.prev.prev.value
1221 print i.next.next.next.prev.prev.prev.value
1223 ##!!! this should be True both ways...
1224 print CONTENT((2, 3, 4)) == range(10)
1225 print range(10) == CONTENT((2, 3, 4))
1229 #=======================================================================
1230 # vim:set ts=4 sw=4 nowrap :