Source code for humpack.basic_containers


from collections import OrderedDict, deque
import heapq

from .utils import safe_self_execute
from .errors import LoadInitFailureError
from .packing import Packable, pack_member, unpack_member
from .transactions import Transactionable
from .hashing import Hashable

[docs]class Container(Transactionable, Packable, Hashable): pass
# keys must be primitives, values can be primitives or Packable instances/subclasses
[docs]class tdict(Container, OrderedDict): ''' Humpack dictionary, replaces the standard dict Has all the same functionality of a dict, plus being Transactionable or Packable ''' def __new__(cls, *args, **kwargs): self = super().__new__(cls) self.__dict__['_data'] = OrderedDict() self.__dict__['_shadow'] = None return self
[docs] def __init__(self, *args, **kwargs): super().__init__() self.__dict__['_data'] = OrderedDict(*args, **kwargs)
[docs] def in_transaction(self): return self._shadow is not None
[docs] def begin(self): if self.in_transaction(): return self.commit() # partial transactions are committed self._shadow = self._data self._data = self._data.copy() for child in self.values(): # if keys could be Transactionable instances: chain(self.keys(), self.values()) if isinstance(child, Transactionable): child.begin()
[docs] def commit(self): if not self.in_transaction(): return self._shadow = None for child in self.values(): # if keys could be Transactionable instances: chain(self.keys(), self.values()) if isinstance(child, Transactionable): child.commit()
[docs] def abort(self): if not self.in_transaction(): return self._data = self._shadow self._shadow = None for child in self.values(): # if keys could be Transactionable instances: chain(self.keys(), self.values()) if isinstance(child, Transactionable): child.abort()
[docs] def todict(self): return {k:v for k,v in self.items()}
[docs] def update(self, other): self._data.update(other)
[docs] def fromkeys(self, keys, value=None): self._data.fromkeys(keys, value)
[docs] def clear(self): self._data.clear()
[docs] def copy(self): copy = type(self)() copy._data = self._data.copy() if self._shadow is not None: copy._shadow = self._shadow.copy() return copy
[docs] def __len__(self): return len(self._data)
[docs] def __hash__(self): return id(self)
[docs] def __eq__(self, other): return id(self) == id(other)
[docs] def __contains__(self, item): return self._data.__contains__(item)
[docs] def __reversed__(self): return self._data.__reversed__()
[docs] def __iter__(self): return iter(self._data)
[docs] def keys(self): return self._data.keys()
[docs] def values(self): return self._data.values()
[docs] def items(self): return self._data.items()
[docs] def pop(self, key): return self._data.pop(key)
[docs] def popitem(self): return self._data.popitem()
[docs] def move_to_end(self, key, last=True): self._data.move_to_end(key, last)
[docs] def __pack__(self): data = {} data['_pairs'] = {} data['_order'] = [] for key, value in self.items(): k, v = pack_member(key, force_str=True), pack_member(value) data['_pairs'][k] = v data['_order'].append(k) if self.in_transaction(): # TODO: maybe write warning about saving in the middle of a transaction data['_shadow_pairs'] = {} data['_shadow_order'] = [] for key, value in self._shadow.items(): k, v = pack_member(key, force_str=True), pack_member(value) data['_shadow_pairs'][k] = v data['_shadow_order'].append(k) return data
[docs] def __unpack__(self, data): # TODO: write warning about overwriting state - which can't be aborted # if self.in_transaction(): # pass self.abort() self._data.clear() for key in data['_order']: self._data[unpack_member(key)] = unpack_member(data['_pairs'][key]) if '_shadow_pairs' in data: # TODO: maybe write warning about loading into a partially completed transaction self._shadow = OrderedDict() for key in data['_shadow_order']: self._shadow[unpack_member(key)] = unpack_member(data['_shadow_pairs'][key]) return self
[docs] def get(self, k, *args, **kwargs): return self._data.get(k, *args, **kwargs)
[docs] def setdefault(self, key, default=None): self._data.setdefault(key, default)
[docs] def __getitem__(self, item): return self._data[item]
[docs] def __setitem__(self, key, value): # TODO: write warning if key is not a primitive, subclass of Packable, or instance of Packable self._data[key] = value
[docs] def __delitem__(self, key): del self._data[key]
[docs] def __str__(self, default='{...}'): return safe_self_execute(self, lambda: 't{}{}{}'.format('{', ', '.join(str(key) for key in iter(self)), '}'), default=default, flag='self printed flag')
[docs] def __repr__(self, default='{...}'): return safe_self_execute(self, lambda: 't{}{}{}'.format('{', ', '.join( ('{}:{}'.format(repr(key), repr(value)) for key, value in self.items())), '}'), default=default, flag='self printed flag')
[docs]class adict(tdict):
[docs] def __getattr__(self, item): if item in self.__dict__: return super().__getattribute__(item) return self.__getitem__(item)
[docs] def __setattr__(self, key, value): if key in self.__dict__: return super().__setattr__(key, value) return self.__setitem__(key, value)
[docs] def __delattr__(self, item): if item in self.__dict__: # raise Exception('{} cannot be deleted'.format(item)) return super().__delattr__(item) return self.__delitem__(item)
[docs]class tlist(Container, list): ''' Humpack list, replaces the standard list Has all the same functionality of a list, plus being Transactionable or Packable ''' def __new__(cls, *args, **kwargs): self = super().__new__(cls) self._data = [] self._shadow = None return self
[docs] def __init__(self, *args, **kwargs): super().__init__() self._data = list(*args, **kwargs)
[docs] def in_transaction(self): return self._shadow is not None
[docs] def begin(self): if self.in_transaction(): return self.commit() # partial transactions are committed self._shadow = self._data self._data = self._data.copy() for child in iter(self): if isinstance(child, Transactionable): child.begin()
[docs] def commit(self): if not self.in_transaction(): return self._shadow = None for child in iter(self): if isinstance(child, Transactionable): child.commit()
[docs] def abort(self): if not self.in_transaction(): return self._data = self._shadow self._shadow = None for child in iter(self): if isinstance(child, Transactionable): child.abort()
[docs] def tolist(self): return [x for x in self]
[docs] def copy(self): copy = type(self)() copy._data = self._data.copy() if self._shadow is not None: copy._shadow = self._shadow.copy() return copy
[docs] def __pack__(self): state = {} state['_entries'] = [pack_member(elm) for elm in iter(self)] if self.in_transaction(): # TODO: maybe write warning about saving in the middle of a transaction state['_shadow'] = [pack_member(elm) for elm in self._shadow] return state
[docs] def __unpack__(self, state): # TODO: write warning about overwriting state - which can't be aborted # if self.in_transaction(): # pass self._data.extend(unpack_member(elm) for elm in state['_entries']) if '_shadow' in state: # TODO: maybe write warning about loading into a partially completed transaction self._shadow = [unpack_member(elm) for elm in state['_shadow']]
[docs] def __getitem__(self, item): if isinstance(item, slice): return tlist(self._data[item]) return self._data[item]
[docs] def __setitem__(self, key, value): self._data[key] = value
[docs] def __delitem__(self, idx): del self._data[idx]
[docs] def __hash__(self): return id(self)
[docs] def __eq__(self, other): return id(self) == id(other)
[docs] def count(self, object): return self._data.count(object)
[docs] def append(self, item): return self._data.append(item)
[docs] def __contains__(self, item): return self._data.__contains__(item)
[docs] def extend(self, iterable): return self._data.extend(iterable)
[docs] def insert(self, index, object): self._data.insert(index, object)
[docs] def remove(self, value): self._data.remove(value)
[docs] def __iter__(self): return iter(self._data)
[docs] def __reversed__(self): return self._data.__reversed__()
[docs] def reverse(self): self._data.reverse()
[docs] def pop(self, index=None): if index is None: return self._data.pop() return self._data.pop(index)
[docs] def __len__(self): return len(self._data)
[docs] def clear(self): self._data.clear()
[docs] def sort(self, key=None, reverse=False): self._data.sort(key=key, reverse=reverse)
[docs] def index(self, object, start=None, stop=None): self._data.index(object, start, stop)
[docs] def __mul__(self, other): return tlist(self._data.__mul__(other))
[docs] def __rmul__(self, other): return tlist(self._data.__rmul__(other))
[docs] def __add__(self, other): out = self.copy() out.extend(other) return out
[docs] def __iadd__(self, other): self._data.__iadd__(other)
[docs] def __imul__(self, other): self._data.__imul__(other)
[docs] def __str__(self, default='[...]'): return safe_self_execute(self, lambda: 't[{}]'.format(', '.join(map(str, self))), default=default, flag='self printed flag')
[docs] def __repr__(self, default='[...]'): return safe_self_execute(self, lambda: 't[{}]'.format(', '.join(map(repr, self))), default=default, flag='self printed flag')
[docs]class tset(Container, set): ''' Humpack set, replaces the standard set Has all the same functionality of a set, plus being Transactionable or Packable ''' def __new__(cls, *args, **kwargs): self = super().__new__(cls) self._data = OrderedDict() self._shadow = None return self
[docs] def __init__(self, iterable=[]): super().__init__() for x in iterable: self.add(x) self._shadow = None
[docs] def in_transaction(self): return self._shadow is not None
[docs] def begin(self): if self.in_transaction(): return self.commit() # partial transactions are committed self._shadow = self._data self._data = self._data.copy() for child in iter(self): if isinstance(child, Transactionable): child.begin()
[docs] def commit(self): if not self.in_transaction(): return self._shadow = None for child in iter(self): if isinstance(child, Transactionable): child.commit()
[docs] def abort(self): if not self.in_transaction(): return self._data = self._shadow self._shadow = None for child in iter(self): if isinstance(child, Transactionable): child.abort()
[docs] def toset(self): return {x for x in self}
[docs] def copy(self): copy = type(self)() copy._data = self._data.copy() if self._shadow is not None: copy._shadow = self._shadow.copy() return copy
[docs] def __pack__(self): state = {} state['_elements'] = [pack_member(elm) for elm in iter(self)] if self.in_transaction(): state['_shadow'] = [pack_member(elm) for elm in self._shadow] return state
[docs] def __unpack__(self, data): # TODO: write warning about overwriting state - which can't be aborted # if self.in_transaction(): # pass self.update(unpack_member(elm) for elm in data['_elements']) if '_shadow' in data: # TODO: maybe write warning about loading into a partially completed transaction self._shadow = OrderedDict() for elm in data['_shadow']: self._shadow[unpack_member(elm)] = None
[docs] def __hash__(self): return id(self)
[docs] def __eq__(self, other): return id(self) == id(other)
[docs] def __and__(self, other): copy = self.copy() for x in self: if x in other: copy.add(x) else: copy.remove(x) return copy
[docs] def __or__(self, other): copy = self.copy() copy.update(other) return copy
[docs] def __xor__(self, other): copy = self.copy() for x in list(other): if x in other: copy.add(x) else: copy.remove(x) return copy
[docs] def __sub__(self, other): copy = self.copy() for x in other: copy.discard(x) return copy
[docs] def __rand__(self, other): return self & other
[docs] def __ror__(self, other): return self | other
[docs] def __rxor__(self, other): return self ^ other
[docs] def __rsub__(self, other): copy = other.copy() for x in self: copy.discard(x) return copy
[docs] def difference_update(self, other): self -= other
[docs] def intersection_update(self, other): self &= other
[docs] def union_update(self, other): self |= other
[docs] def symmetric_difference_update(self, other): self ^= other
[docs] def symmetric_difference(self, other): return self ^ other
[docs] def union(self, other): return self | other
[docs] def intersection(self, other): return self & other
[docs] def difference(self, other): return self - other
[docs] def issubset(self, other): for x in self: if x not in other: return False return True
[docs] def issuperset(self, other): for x in other: if x not in self: return False return True
[docs] def isdisjoint(self, other): return not self.issubset(other) and not self.issuperset(other)
[docs] def __iand__(self, other): for x in list(self): if x not in other: self.remove(x)
[docs] def __ior__(self, other): self.update(other)
[docs] def __ixor__(self, other): for x in other: if x in self: self.remove(x) else: self.add(x)
[docs] def __isub__(self, other): for x in other: if x in self: self.remove(x)
[docs] def pop(self): return self._data.popitem()[0]
[docs] def remove(self, item): del self._data[item]
[docs] def discard(self, item): if item in self._data: self.remove(item)
[docs] def __contains__(self, item): return self._data.__contains__(item)
[docs] def __len__(self): return len(self._data)
[docs] def __iter__(self): return iter(self._data)
[docs] def clear(self): return self._data.clear()
[docs] def update(self, other): for x in other: self.add(x)
[docs] def add(self, item): self._data[item] = None
[docs] def __str__(self, default='{...}'): return safe_self_execute(self, lambda: 't{}{}{}'.format('{', ', '.join(map(str, self)), '}'), default=default, flag='self printed flag')
[docs] def __repr__(self, default='{...}'): return safe_self_execute(self, lambda: 't{}{}{}'.format('{', ', '.join(map(repr, self)), '}'), default=default, flag='self printed flag')
[docs]class tdeque(Container, deque): ''' Humpack queue, replaces the standard deque Has all the same functionality of a set, plus being Transactionable or Packable ''' def __new__(cls, *args, **kwargs): self = super().__new__(cls) self._data = deque() self._shadow = None return self
[docs] def __init__(self, *args, **kwargs): super().__init__() self._data = deque(*args, **kwargs)
[docs] def in_transaction(self): return self._shadow is not None
[docs] def begin(self): if self.in_transaction(): return self.commit() # partial transactions are committed self._shadow = self._data self._data = self._data.copy() for child in iter(self): if isinstance(child, Transactionable): child.begin()
[docs] def commit(self): if not self.in_transaction(): return self._shadow = None for child in iter(self): if isinstance(child, Transactionable): child.commit()
[docs] def abort(self): if not self.in_transaction(): return self._data = self._shadow self._shadow = None for child in iter(self): if isinstance(child, Transactionable): child.abort()
[docs] def copy(self): copy = type(self)() copy._data = self._data.copy() if self._shadow is not None: copy._shadow = self._shadow.copy() return copy
[docs] def __pack__(self): state = {} state['_entries'] = [pack_member(elm) for elm in iter(self)] if self.in_transaction(): # TODO: maybe write warning about saving in the middle of a transaction state['_shadow'] = [pack_member(elm) for elm in self._shadow] return state
[docs] def __unpack__(self, state): # TODO: write warning about overwriting state - which can't be aborted # if self.in_transaction(): # pass self._data.extend(unpack_member(elm) for elm in state['_entries']) if '_shadow' in state: # TODO: maybe write warning about loading into a partially completed transaction self._shadow = [unpack_member(elm) for elm in state['_shadow']]
[docs] def __getitem__(self, item): if isinstance(item, slice): return tdeque(self._data[item]) return self._data[item]
[docs] def __setitem__(self, key, value): self._data[key] = value
[docs] def __delitem__(self, idx): del self._data[idx]
[docs] def __hash__(self): return id(self)
[docs] def __eq__(self, other): return id(self) == id(other)
[docs] def count(self, object): return self._data.count(object)
[docs] def append(self, item): return self._data.append(item)
[docs] def appendleft(self, item): return self._data.appendleft(item)
[docs] def __contains__(self, item): return self._data.__contains__(item)
[docs] def extend(self, iterable): return self._data.extend(iterable)
[docs] def extendleft(self, iterable): return self._data.extendleft(iterable)
[docs] def insert(self, index, object): self._data.insert(index, object)
[docs] def remove(self, value): self._data.remove(value)
[docs] def __iter__(self): return iter(self._data)
[docs] def __reversed__(self): return self._data.__reversed__()
[docs] def reverse(self): self._data.reverse()
[docs] def pop(self): return self._data.pop()
[docs] def popleft(self): return self._data.popleft()
[docs] def __len__(self): return len(self._data)
[docs] def clear(self): self._data.clear()
[docs] def sort(self, key=None, reverse=False): self._data.sort(key, reverse)
[docs] def index(self, object, start=None, stop=None): self._data.index(object, start, stop)
[docs] def rotate(self, n=1): return self._data.rotate(n=n)
[docs] def __mul__(self, other): return tdeque(self._data.__mul__(other))
[docs] def __rmul__(self, other): return tdeque(self._data.__rmul__(other))
[docs] def __add__(self, other): out = self.copy() out.extend(other) return out
[docs] def __iadd__(self, other): self._data.__iadd__(other)
[docs] def __imul__(self, other): self._data.__imul__(other)
[docs] def __str__(self, default='[...]'): return safe_self_execute(self, lambda: 't[{}]'.format(', '.join(map(str, self))), default=default, flag='self printed flag')
[docs] def __repr__(self, default='[...]'): return safe_self_execute(self, lambda: 't[{}]'.format(', '.join(map(repr, self))), default=default, flag='self printed flag')
[docs]class tstack(tdeque): ''' Humpack stack Has all the same functionality of a deque, except it's a stack (FIFO) Also implements Transactionable and Packable '''
[docs] def pop(self): return super().popleft()
[docs] def popend(self): return super().pop()
[docs] def push(self, item): return super().appendleft(item)
[docs] def push_all(self, items): return super().extendleft(reversed(items))
[docs] def peek(self, n=0): return self[n]
[docs]class _theap_iter(object):
[docs] def __init__(self, heap): self._heap = heap
[docs] def __next__(self): if len(self._heap): return self._heap.pop() raise StopIteration
[docs]class theap(Container, object): ''' Humpack heap Unordered for adding/removing, ordered when iterating. Note that iterating through the heap empties it. ''' def __new__(cls, *args, **kwargs): self = super().__new__(cls) self._data = [] self._shadow = None return self
[docs] def __init__(self, *args, **kwargs): super().__init__() self._data = list(*args, **kwargs) heapq.heapify(self._data)
[docs] def in_transaction(self): return self._shadow is not None
[docs] def begin(self): if self.in_transaction(): return self.commit() # partial transactions are committed self._shadow = self._data self._data = self._data.copy() for child in iter(self): if isinstance(child, Transactionable): child.begin()
[docs] def commit(self): if not self.in_transaction(): return self._shadow = None for child in iter(self): if isinstance(child, Transactionable): child.commit()
[docs] def abort(self): if not self.in_transaction(): return self._data = self._shadow self._shadow = None for child in iter(self): if isinstance(child, Transactionable): child.abort()
[docs] def copy(self): copy = type(self)() copy._data = self._data.copy() if self._shadow is not None: copy._shadow = self._shadow.copy() return copy
[docs] def __pack__(self): state = {} state['_entries'] = [pack_member(elm) for elm in iter(self)] if self.in_transaction(): # TODO: maybe write warning about saving in the middle of a transaction state['_shadow'] = [pack_member(elm) for elm in self._shadow] return state
[docs] def __unpack__(self, state): # TODO: write warning about overwriting state - which can't be aborted # if self.in_transaction(): # pass self._data.extend(unpack_member(elm) for elm in state['_entries']) if '_shadow' in state: # TODO: maybe write warning about loading into a partially completed transaction self._shadow = [unpack_member(elm) for elm in state['_shadow']]
[docs] def __iter__(self): # Note: this actually pops entries - iterating through heap will empty it return _theap_iter(self.copy())
[docs] def __len__(self): return len(self._data)
[docs] def push(self, *items): for item in items: heapq.heappush(self._data, item)
[docs] def pop(self, n=None): if n is None: return heapq.heappop(self._data) return tlist(heapq.heappop(self._data) for _ in range(n))
[docs] def replace(self, item): return heapq.heapreplace(self._data, item)
[docs] def pushpop(self, item): return heapq.heappushpop(self._data, item)
[docs] def __hash__(self): return id(self)
[docs] def __eq__(self, other): return id(self) == id(other)
[docs] def __str__(self, default='[...]'): return safe_self_execute(self, lambda: 't[{}]'.format(', '.join(map(str, self._data))), default=default, flag='self printed flag')
[docs] def __repr__(self, default='[...]'): return safe_self_execute(self, lambda: 't[{}]'.format(', '.join(map(repr, self._data))), default=default, flag='self printed flag')
[docs]def containerify(obj, dtype=tdict): ''' Recursively, convert `obj` from using standard python containers to HumPack containers. :param obj: object using python containers (dict, list, set, tuple, etc.) :return: deep copy of the object using HumPack containers ''' if isinstance(obj, deque): return tdeque(containerify(o, dtype=dtype) for o in obj) if isinstance(obj, list): return tlist(containerify(o, dtype=dtype) for o in obj) if isinstance(obj, set): return tset(containerify(o, dtype=dtype) for o in obj) if isinstance(obj, tuple): return tuple(containerify(o, dtype=dtype) for o in obj) if isinstance(obj, dict): return dtype({containerify(k, dtype=dtype): containerify(v, dtype=dtype) for k, v in obj.items()}) return obj