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