1 """
2 Generic containers, data structures and language extensions.
3
4 This module has several functions:
5
6 1. provides a set of reusable, probably encapsulated collection containers
7 and supporting infrastructure: L{BaseDictionaryContainer},
8 L{BaseCollectionContainer}; also L{AbstractIndexer}
9
10 2. provides some missing generic data structures or data types:
11 L{OrderedDict}, L{Stack}; also the heavily used, type-safe L{enum} class
12 and some generic pattern implementations like L{singleton} or L{Proxy}
13
14 3. serves as a compatibility layer, making it possible to develop CSB as
15 platform- and Python version-independent: string, L{iterable}, L{metaclass}
16
17 In order to ensure cross-interpreter compatibility, checking for string instances
18 in CSB must always be implemented like this:
19
20 >>> isinstance("s", string)
21
22 because "basestring" is not available in Python 3. Also, metaclass definitions
23 other than abstract classes must be implemented as follows:
24
25 >>> MyClassBase = metaclass(MetaClass, base=BaseClass)
26 >>> class MyClass(MyClassBase):
27 pass
28
29 See also the notes about compatibility in L{csb.io}.
30 """
31
32 import re
33 import sys
34 import time
35 import threading
36
37 from abc import ABCMeta, abstractproperty, abstractmethod
38
39 try:
40 string = basestring
41 except NameError:
42 string = str
46
47 - def __init__(self, target, name=None, args=[], kwargs={}):
56
57 @property
60
62 try:
63 self.__result = self.__target(*self.__args, **self.__kwargs)
64 except Exception as ex:
65 sys.stderr.write(ex)
66 self.__result = None
67
69 """
70 Singleton class decorator.
71 """
72 instances = {}
73
74 def get():
75 if klass not in instances:
76 instances[klass] = klass()
77 return instances[klass]
78
79 return get
80
82 """
83 Base class implementing the proxy pattern. Subclasses that define
84 a customized constructor must call super().__init__(subject) for proper
85 initialization.
86
87 @param subject: an arbitrary object, hidden behind the proxy
88 """
89
92
94 try:
95 return object.__getattribute__(self, name)
96 except AttributeError:
97 subject = object.__getattribute__(self, '_subject')
98 return getattr(subject, name)
99
101
103
104 self._start = [match.start()]
105 self._end = [match.end()]
106 self._groups = match.groups()
107
108 for i, dummy in enumerate(self._groups, start=1):
109 self._start.append(match.start(i))
110 self._end.append(match.end(i))
111
112 - def start(self, group=0):
113 try:
114 if not group >= 0:
115 raise IndexError
116 return self._start[group]
117 except IndexError:
118 raise IndexError('no such group')
119
120 - def end(self, group=0):
121 try:
122 if not group >= 0:
123 raise IndexError
124 return self._end[group]
125 except IndexError:
126 raise IndexError('no such group')
127
130
132
133 - def push(self, item):
134 """
135 Push an item onto the top of the stack
136 """
137 self.append(item)
138
140 """
141 Return the object at the top of the stack without removing it
142 """
143 if self.empty():
144 raise IndexError('peek in empty list')
145 return self[-1]
146
148 """
149 Return True if the stack is empty
150 """
151 return len(self) == 0
152
154 """
155 Return True if C{obj} is a collection or iterator, but not string.
156 This is guaranteed to work in both python 2 and 3.
157
158 @rtype: bool
159 """
160 if hasattr(obj, '__iter__'):
161 if not isinstance(obj, string):
162 return True
163
164 return False
165
172
174 """
175 Perform a deep copy of obj using cPickle. Faster than copy.deepcopy()
176 for large objects.
177
178 @param obj: the object to copy
179 @return: a deep copy of obj
180 @param recursion: maximum recursion limit
181 @type recursion: int
182 """
183 from csb.io import Pickle
184
185 current = sys.getrecursionlimit()
186 sys.setrecursionlimit(recursion)
187
188 tmp = Pickle.dumps(obj, Pickle.HIGHEST_PROTOCOL)
189 copy = Pickle.loads(tmp)
190
191 sys.setrecursionlimit(current)
192 return copy
193
196
199
202
204
206 self.__name = name
207 self.__value = value
208 self.__container = enum
209
211 return (_deserialize_enum, (self.enum, self.name))
217 return '{0}'.format(self.__name, self.__value)
219 return str(self.__value)
221 return int(self.__value)
223 return float(self.__value)
225 return hash(self.__value)
227 if isinstance(other, EnumItem):
228 return self.__value < other.value
229 else:
230 return self.__value < other
232 if isinstance(other, EnumItem):
233 return self.__value == other.value
234 else:
235 return self.__value == other
237 return not (self == other)
238
239 @property
242
243 @property
246
247 @property
249 return self.__container
250
252 assert getattr(enum, self.__name) is self
253 self.__container = enum
254
308
309 EnumBase = metaclass(EnumMeta)
310
311 -class enum(EnumBase):
312 """
313 Base class for all enumeration types. Supports both string and integer
314 enumeration values. Examples:
315
316 >>> class MolTypes(enum): DNA, RNA = range(2)
317 <MolTypes: RNA=1, DNA=0>
318 >>> class MolTypes(enum): DNA=1; RNA=2
319 <MolTypes: RNA=2, DNA=1>
320 >>> MolTypes.DNA
321 1
322 >>> MolTypes.DNA == 1
323 True
324 >>> int(MolTypes.DNA)
325 1
326 >>> repr(MolTypes.DNA)
327 'DNA'
328 >>> type(MolTypes.DNA)
329 L{EnumItem} instance
330
331 @note: The recommended way to create an enum is to define a public
332 subclass of L{enum} in the global namespace of your module. Nesting
333 the enum in another class for example will break pickling.
334 """
336 raise TypeError("Can't instantiate static enum type {0}".format(self.__class__))
337
339 """
340 A collection of efficient static methods for working with L{enum}
341 classes.
342 """
343
344 @staticmethod
345 - def create(classname, **members):
346 """
347 Dynamically create a new enum from a list of key:value pairs.
348 Note that each key must be a valid python identifier, and the
349 values must be unique.
350
351 @param classname: class name for the new enum
352 @type classname: str
353
354 @note: The recommended way to create an enum is to define a public
355 subclass of L{enum} in the global namespace of your module.
356 You should avoid creating enums dynamically if static
357 construction is possible, because dynamically created enums
358 cannot be pickled.
359 """
360
361 return type(classname, (enum,), members)
362
363 @staticmethod
365 """
366 Return all member items of the C{enumclass}.
367
368 @param enumclass: the enumeration class to traverse
369 @type enumclass: type
370
371 @return: a set of all enumclass members
372 @rtype: frozenset
373 """
374 return frozenset([enumclass.__dict__[i] for i in enumclass._names])
375
376 @staticmethod
378 """
379 Return the names of all items in the C{enumclass}.
380
381 @param enumclass: the enumeration class to traverse
382 @type enumclass: type
383
384 @return: a set of all enumclass member names
385 @rtype: frozenset
386 """
387 return frozenset(enumclass._names)
388
389 @staticmethod
391 """
392 Return all values of the C{enumclass}.
393
394 @param enumclass: the enumeration class to traverse
395 @type enumclass: type
396
397 @return: a set of all enum values
398 @rtype: frozenset
399 """
400 return frozenset(enumclass._values)
401
402 @staticmethod
403 - def parse(enumclass, value, ignore_case=True):
404 """
405 Parse C{value} as an C{enumclass} member value.
406
407 @param enumclass: target L{enum} subclass
408 @type enumclass: type
409 @type value: str, int, float
410 @param ignore_case: if set to True, triggers case insensitive parsing
411 @type ignore_case: bool
412
413 @return: a member of enumclass, having such value
414 @rtype: L{EnumItem}
415
416 @raise EnumValueError: when such value is not found in enumclass
417 """
418
419 if isinstance(value, string) and ignore_case:
420 values = enumclass._valuesci
421 value = value.lower()
422 else:
423 values = enumclass._values
424
425 if value in values:
426 return enumclass.__dict__[ values[value] ]
427 else:
428 raise EnumValueError('No such value {0} in {1}'.format(value, enumclass))
429
430 @staticmethod
431 - def parsename(enumclass, name, ignore_case=True):
432 """
433 Parse C{name} as a member of C{enumclass}.
434
435 @param enumclass: target L{enum} subclass
436 @type enumclass: type
437 @param name: enumclass member name to parse
438 @type name: str
439 @param ignore_case: if set to True, triggers case insensitive parsing
440 @type ignore_case: bool
441
442 @return: a member of enumclass, having such name
443 @rtype: L{EnumItem}
444
445 @raise EnumValueError: when such name is not found in enumclass's members
446 """
447
448 if isinstance(name, string) and ignore_case:
449 names = enumclass._namesci
450 name = name.lower()
451 else:
452 names = enumclass._names
453
454 if name in names:
455 return enumclass.__dict__[ names[name] ]
456 else:
457 raise EnumMemberError('No such item {0} in {1}'.format(name, enumclass))
458
459 @staticmethod
461 """
462 Return a string representation of the enum item.
463
464 @param item: an enum item to be converted to a string
465 @type item: L{EnumItem}
466
467 @return: the value of the enum member
468 @rtype: str
469 """
470 return item.name
471
472 @staticmethod
474 """
475 Return True if item is a member of enumclass.
476
477 @param enumclass: target enumeration type
478 @type enumclass: type
479 @param item: the enum item to be tested
480 @type item: L{EnumItem}
481 @rtype: bool
482 """
483 if not issubclass(enumclass, enum):
484 raise TypeError(enumclass)
485 return item.enum is enumclass
486
489
492
495
497
498 @abstractmethod
501
502 @abstractmethod
504 """
505 Implementing classes are expected to provide direct access to the
506 requested element in the internal data structure via this method.
507 """
508 pass
509
511 """
512 Base class which defines the behavior of a read only key-value collection
513 container.
514
515 @note: Methods for editing an existing dictionary are also defined in the
516 base class, but left internal on purpose. Subclasses which are
517 supposed to be write-enabled containers must provide their own
518 public methods for editing which might do some custom work and then
519 finally call any of the internal methods in the base class to do the
520 real data editing.
521
522 @param items: an initialization dictionary
523 @param restrict: a list of keys allowed for this dictionary
524 """
525
526 - def __init__(self, items=None, restrict=None):
535
537 try:
538 return self._items[key]
539 except KeyError:
540 raise self._exception(key)
541
543 return self._items[key]
544
546 return item in self._items
547
549 return len(self._items)
550
553
556
557 @property
560
561 @property
564
566 return self._items.keys()
567
569 return iter(self._items)
570
572 return '<{0.__class__.__name__}: {0.length} items>'.format(self)
573
575 new_items = OrderedDict(new_items)
576
577 if self._keys and not self._keys.issuperset(new_items):
578 raise InvalidKeyError("One or more of the keys provided are not allowed for this collection.")
579
580 self._items = new_items
581
583 new_items = OrderedDict(new_items)
584
585 if self._keys:
586 if not set(self).issuperset(new_items) or not set(self).issuperset(named_items):
587 raise ItemNotFoundError("One or more of the keys provided were not found in this collection.")
588
589 self._items.update(new_items)
590 self._items.update(named_items)
591
593
594 if self._keys and key not in self._keys:
595 raise InvalidKeyError("Key {0} is not allowed for this collection.".format(key))
596 if key in self:
597 raise DuplicateKeyError("Key {0} already exists in this collection.".format(key))
598 self._items[key] = item
599
601
602 if key not in self._items:
603 raise self._exception(key)
604 del self._items[key]
605
607 """
608 This is a write-once container, which is filled with items only at object
609 construction.
610 """
611 pass
612
614 """
615 Write-enabled Dictionary Container. New items can be added using a public
616 C{append} method. Subclasses may also override the internal C{_update}
617 and C{_set} to expose them to the clients.
618 """
619 - def __init__(self, items=None, restrict=None):
622
624 """
625 Append a new key-value to the collection.
626
627 @param key: key
628 @param item: value
629
630 @raise InvalidKeyError: when the key is not allowed for this container
631 @raise DuplicateKeyError: when such a key already exists
632 """
633 self._append_item(key, item)
634
635 - def _set(self, new_items):
636 """
637 Set the collection to the dictionary provided in the new_items parameter.
638
639 @param new_items: the new value of the dictionary container
640 @type new_items: dict
641 """
642 self._set_items(new_items)
643
644 - def _update(self, new_items={ }, **named_items):
645 """
646 Update the collection with the dictionary provided in the C{new_items} parameter.
647 Update also specific items with the values provided as keyword arguments.
648
649 @param new_items: a dictionary that provides new values for certain keys
650 @type new_items: dict
651 """
652 self._update_items(new_items, **named_items)
653
655 """
656 Delete C{item}.
657
658 @param item: key to remove
659 """
660 self._remove_item(item)
661
664
666 """
667 Base class which defines the behavior of a read-only collection container.
668
669 @note: Methods for editing an existing collection are also defined in the
670 base class, but left internal on purpose. Subclasses which are
671 supposed to be write-enabled containers must provide their own
672 public methods for editing which might do some custom work and then
673 finally call any of the internal methods in the base class to do the
674 real data editing.
675
676 @param items: initialization list
677 @type items: list
678 @param type: if defined, makes the container type-safe
679 @type type: type
680 @param start_index: the index of the zero element
681 @type start_index: int
682 """
683
684 - def __init__(self, items=None, type=None, start_index=0):
685
686 self._items = [ ]
687 self._type = type
688
689 if not (isinstance(start_index, int) or start_index >= 0):
690 raise ValueError('start_index must be a positive integer.')
691
692 self._start = start_index
693
694 if items is not None:
695 self._update_items(items)
696
698 if i is not None and i >= 0:
699 if i < self._start:
700 return None
701 return i - self._start
702 else:
703 return i
704
706 try:
707 if isinstance(i, slice):
708 sl = slice(self.__fix(i.start), self.__fix(i.stop), i.step)
709 return self._items[sl]
710 else:
711 if 0 <= i < self._start:
712 raise IndexError
713 return self._items[self.__fix(i)]
714
715 except IndexError:
716 if len(self) > 0:
717 raise self._exception('Position {0} is out of range [{1}, {2}]'.format(
718 i, self.start_index, self.last_index))
719 else:
720 raise self._exception('This collection is empty.')
721
723 return self._items[i]
724
726 return item in self._items
727
729 return len(self._items)
730
733
736
737 @property
740
741 @property
744
745 @property
748
749 @property
751 length = len(self._items)
752 if length > 0:
753 return length + self._start - 1
754 else:
755 return None
756
758 return iter(self._items)
759
761 return '<{0.__class__.__name__}: {0.length} items>'.format(self)
762
764
765 if self._type:
766 if not isinstance(item, self._type):
767 raise TypeError("Item {0} is not of the required {1} type.".format(
768 item, self._type.__name__))
769 self._items.append(item)
770
771 return len(self) + self._start - 1
772
774
775 if self._type:
776 for a in new_items:
777 if not isinstance(a, self._type):
778 raise TypeError("Item {0} is not of the required {1} type.".format(
779 a, self._type.__name__))
780 self._items = list(new_items)
781
784
786 """
787 This is a write-once container, which is filled with items only at object
788 construction.
789 """
790 pass
791
793 """
794 Write-enabled Collection Container. New items can be added using a public
795 C{append} method. Subclasses may also override the internal C{_update}
796 to expose it to the clients.
797 """
798
799 - def __init__(self, items=None, type=None, start_index=0):
802
804 """
805 Append a new item to the collection.
806
807 @param item: the new item to append
808
809 @return: the index of the new element
810 @rtype: int
811
812 @raise TypeError: when the container is type-safe and item has an
813 incorrect type
814 """
815 return self._append_item(item)
816
818 """
819 Set the collection to the list provided in the new_items parameter.
820
821 @param new_items: a list providing the new items for the container
822 @type new_items: list
823 """
824 self._update_items(new_items)
825
827 """
828 Defines the behavior of a high-level object, which can hold an array of
829 elements. Implementing classes automatically provide iterable and index/key
830 based access to those objects in a read-only encapsulated manner.
831
832 This is an abstract class with an abstract property C{_children}. Subclasses
833 must override this property. The overridden implementation is usually
834 extremely simple - you just need to return a reference to an iterable and
835 subscriptable object, containing the elements.
836 """
837
838 __metaclass__ = ABCMeta
839
841 return self._children[i]
842
844 return len(self._children)
845
848
849 __bool__ = __nonzero__
850
852 for i in self._children:
853 yield i
854
855 @abstractproperty
858
860 """
861 Generic implementation of L{AbstractContainer}. Provides an easy way to
862 encapsulate class properties that behave like read-only collections or
863 dictionaries. This container is super lightweight and completely dynamic:
864 it serves as a proxy to an internal list or dict and stores no data in its
865 own instance. Owning classes are therefore free to modify those internal
866 data structures, which provides advantages over using
867 L{ReadOnlyCollectionContainer}s.
868 """
869
872
873 @property
876
878 """
879 Same as the L{AbstractContainer}, but provides access to the child elements
880 through C{AbstractNativeIndexer._getinternal} instead of the standard
881 __getitem__.
882
883 Therefore, the C{self._children} property must return an object which
884 implements L{AbstractIndexer}.
885 """
887 return self._children._getinternal(i)
888
889 try:
890 from collections import OrderedDict as _dict
893 except ImportError:
894 import UserDict
897
899 if len(args) > 1:
900 raise TypeError('expected at most 1 arguments, got {0}'.format(len(args)))
901 try:
902 self.__end
903 except AttributeError:
904 self.clear()
905 self.update(*args, **kwds)
906
908 self.__end = end = []
909 end += [None, end, end]
910 self.__map = {}
911 dict.clear(self)
912
914 if key not in self:
915 end = self.__end
916 curr = end[1]
917 curr[2] = end[1] = self.__map[key] = [key, curr, end]
918 dict.__setitem__(self, key, value)
919
921 dict.__delitem__(self, key)
922 key, prev, next = self.__map.pop(key)
923 prev[2] = next
924 next[1] = prev
925
927 end = self.__end
928 curr = end[2]
929 while curr is not end:
930 yield curr[0]
931 curr = curr[2]
932
934 end = self.__end
935 curr = end[1]
936 while curr is not end:
937 yield curr[0]
938 curr = curr[1]
939
941 if not self:
942 raise KeyError('dictionary is empty')
943 if last:
944 key = next(reversed(self))
945 else:
946 key = next(iter(self))
947 value = self.pop(key)
948 return key, value
949
951 items = [[k, self[k]] for k in self]
952 tmp = self.__map, self.__end
953 del self.__map, self.__end
954 inst_dict = vars(self).copy()
955 self.__map, self.__end = tmp
956 if inst_dict:
957 return (self.__class__, (items,), inst_dict)
958 return self.__class__, (items,)
959
962
963 setdefault = UserDict.DictMixin.setdefault
964 update = UserDict.DictMixin.update
965 pop = UserDict.DictMixin.pop
966 values = UserDict.DictMixin.values
967 items = UserDict.DictMixin.items
968 iterkeys = UserDict.DictMixin.iterkeys
969 itervalues = UserDict.DictMixin.itervalues
970 iteritems = UserDict.DictMixin.iteritems
971
973 if not self:
974 return '{0}()'.format(self.__class__.__name__)
975 return '{0}({1!r})'.format(self.__class__.__name__, self.items())
976
978 return self.__class__(self)
979
980 @classmethod
981 - def fromkeys(cls, iterable, value=None):
982 d = cls()
983 for key in iterable:
984 d[key] = value
985 return d
986
988 if isinstance(other, OrderedDict):
989 return len(self)==len(other) and self.items() == other.items()
990 return dict.__eq__(self, other)
991
993 return not self == other
994