""" open/DurusWorks/qp/lib/keep.py """ from durus.btree import BTree from durus.persistent import PersistentObject from durus.persistent_dict import PersistentDict from qp.lib.spec import either, datetime_with_tz, mapping, spec, integer from qp.lib.spec import require, get_spec_problems, anything, specify from qp.lib.util import rand_int from qp.lib.tz import UTC class Keyed (object): """ An item with an int key used as a key in an Keep. Note that the key attribute is set when the item is put in the Keep, so there is no set_key() method. This is a mixin for PersistentObject classes. """ key_is = integer __slots__ = [] def __init__(self): assert isinstance(self, PersistentObject) self.key = None def get_key(self): return self.key class KeyCounter (PersistentObject): """ The base class. Generates keys using random 64 bit ints. """ def next(self): """() -> integer This base implementation just uses a big random number. """ return rand_int(64) class Counter (KeyCounter): """ Generates keys by incrementing them. """ next_available_is = integer __slots__ = ['next_available'] def __init__(self): self.next_available = 1 def next(self): result = self.next_available self.next_available = self.next_available + 1 return result class GrowingRandomCounter (KeyCounter): """ Keep a key counter that generates keys randomly, and gradually expands the numbers of bits in the random numbers. The idea is to take advantage of randomly distributed keys, but to try to keep the numbers relatively small. """ bits_is = integer used_is = integer __slots__ = ['bits', 'used'] def __init__(self): specify(self, bits=10, used=0) def next(self): self.used = self.used + 1 # If more than 1% of the possible values have been used, # double the space of possible values. if (self.used / (2 ** self.bits)) > 0.01: self.bits += 1 return rand_int(self.bits) class Keep (object): """ A simple database that stores a mapping of objects by key. This is a mixin for PersistentObject classes. """ value_spec_is = spec( anything, "Specifies the type of values allowed in mapping") mapping_is = mapping({integer:Keyed}, either(PersistentDict, BTree)) key_counter_is = KeyCounter __slots__ = [] def __init__(self, value_spec=Keyed, mapping_class=BTree, counter_class=Counter): assert isinstance(self, PersistentObject) self.mapping = mapping_class() self.value_spec = value_spec self.set_counter(counter_class()) def set_counter(self, counter): specify(self, key_counter=counter) def __len__(self): return len(self.mapping) def get_mapping(self): return self.mapping def get(self, key, default=None): return self.mapping.get(key, default) def itervalues(self): for value in self.mapping.itervalues(): yield value def iterkeys(self): for key in self.mapping.iterkeys(): yield key def iteritems(self): for item in self.mapping.iteritems(): yield item def add(self, value): require(value, self.value_spec) assert value.key is None for attempt in range(1000): value.key = self.key_counter.next() if value.key not in self.mapping: break else: raise KeyError('Unable to find unused key for %r.' % value) if get_spec_problems(value): raise TypeError(''.join(get_spec_problems(value))) self.mapping[value.key] = value def remove(self, value): require(value, self.value_spec) assert value.key is self.mapping[value.key].key del self.mapping[value.key] class Stamped (object): """ Provides a timestamp. This is a mixin for PersistentObject classes. """ stamp_is = datetime_with_tz __slots__ = [] def __init__(self): assert isinstance(self, PersistentObject) self.set_stamp() def get_stamp(self): return self.stamp def set_stamp(self): self.stamp = UTC.now() def stamp_sorted(stamped_sequence): items = [(x.stamp, x) for x in stamped_sequence] return [x for (stamp, x) in sorted(items)] def reverse_stamp_sorted(stamped_sequence): result = stamp_sorted(stamped_sequence) result.reverse() return result