shep

Multi-state key stores using bit masks for python3
git clone git://git.defalsify.org/shep.git
Log | Files | Refs | LICENSE

commit 0eaf032b894ba985c49995e33a4d61cab5e8924e
parent d2bca5134248f49c2935bfda5390b44da69e0250
Author: lash <dev@holbrook.no>
Date:   Mon, 31 Jan 2022 11:23:51 +0000

Add item lifetime controller interface

Diffstat:
MCHANGELOG | 2++
Msetup.cfg | 2+-
Mshep/error.py | 12++++++++++++
Mshep/state.py | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Atests/test_item.py | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 184 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,3 +1,5 @@ +- 0.0.4 + * Add item lifetime control - 0.0.3 * Split match to 2-element tuple, returning complex value and simple values separately - 0.0.2 diff --git a/setup.cfg b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = shep -version = 0.0.3 +version = 0.0.4 description = Multi-state key value stores using bitmaskings author = Louis Holbrook author_email = dev@holbrook.no diff --git a/shep/error.py b/shep/error.py @@ -4,3 +4,15 @@ class StateExists(Exception): class StateInvalid(Exception): pass + + +class StateItemExists(Exception): + pass + + +class StateItemNotFound(Exception): + pass + + +class StateCorruptionError(RuntimeError): + pass diff --git a/shep/state.py b/shep/state.py @@ -1,22 +1,23 @@ -# standard imports -import enum - # local imports from shep.error import ( StateExists, StateInvalid, + StateItemExists, + StateItemNotFound, ) class State: - def __init__(self, bits, logger=None, store=None): + def __init__(self, bits, logger=None, store_factory=None): self.__bits = bits self.__limit = (1 << bits) - 1 self.__c = 0 self.__reverse = {} - self.__logger = logger - self.__store = store + + self.NEW = 0 + self.__items = {self.NEW: []} + self.__items_reverse = {} def __is_pure(self, v): @@ -28,9 +29,13 @@ class State: return c == v - def __check_name(self, k): + def __check_name_valid(self, k): if not k.isalpha(): raise ValueError('only alpha') + + def __check_name(self, k): + self.__check_name_valid(k) + k = k.upper() try: getattr(self, k) @@ -67,6 +72,31 @@ class State: self.__c += 1 + def __check_item(self, item): + if self.__items_reverse.get(item) != None: + raise StateItemExists(item) + + + def __add_state_list(self, state, item): + if self.__items.get(state) == None: + self.__items[state] = [] + self.__items[state].append(item) + self.__items_reverse[item] = state + + + def __state_list_index(self, item, state_list): + idx = -1 + try: + idx = state_list.index(item) + except ValueError: + pass + + if idx == -1: + raise StateCorruptionError() # should have state int here as value + + return idx + + def add(self, k): v = 1 << self.__c k = self.__check_name(k) @@ -111,3 +141,56 @@ class State: c <<= 1 return (alias, r,) + + + def put(self, item, state=None): + if state == None: + state = self.NEW + elif self.__reverse.get(state) == None: + raise StateInvalid(state) + self.__check_item(item) + self.__add_state_list(state, item) + + + def move(self, item, to_state): + current_state = self.__items_reverse.get(item) + if current_state == None: + raise StateItemNotFound(item) + + new_state = self.__reverse.get(to_state) + if new_state == None: + raise StateInvalid(to_state) + + current_state_list = self.__items.get(current_state) + if current_state_list == None: + raise StateCorruptionError(current_state) + + idx = self.__state_list_index(item, current_state_list) + + new_state_list = self.__items.get(to_state) + if current_state_list == None: + raise StateCorruptionError(to_state) + + self.__add_state_list(to_state, item) + current_state_list.pop(idx) + + + + def purge(self, item): + current_state = self.__items_reverse.get(item) + if current_state == None: + raise StateItemNotFound(item) + del self.__items_reverse[item] + + current_state_list = self.__items.get(current_state) + + idx = self.__state_list_index(item, current_state_list) + + current_state_list.pop(idx) + + + def state(self, item): + state = self.__items_reverse.get(item) + if state == None: + raise StateItemNotFound(item) + return state diff --git a/tests/test_item.py b/tests/test_item.py @@ -0,0 +1,79 @@ +# standard imports +import unittest + +# local imports +from shep import State +from shep.error import ( + StateExists, + StateItemExists, + StateInvalid, + StateItemNotFound, + ) + + +class TestStateItems(unittest.TestCase): + + def setUp(self): + self.states = State(4) + self.states.add('foo') + self.states.add('bar') + self.states.add('baz') + self.states.alias('xyzzy', self.states.BAZ | self.states.BAR) + self.states.alias('plugh', self.states.FOO | self.states.BAR) + + + def test_put(self): + item = b'foo' + + # put in initial (no) state + self.states.put(item) + + with self.assertRaises(StateItemExists): + self.states.put(item) + + with self.assertRaises(StateItemExists): + self.states.put(item, self.states.BAZ) + + + def test_item_state(self): + item = b'foo' + self.states.put(item, self.states.XYZZY) + self.assertEqual(self.states.state(item), self.states.XYZZY) + + + def test_item_move(self): + item = b'foo' + self.states.put(item, self.states.FOO) + self.states.move(item, self.states.BAR) + self.assertEqual(self.states.state(item), self.states.BAR) + + + def test_item_move_from_alias(self): + item = b'foo' + self.states.put(item, self.states.FOO) + self.states.move(item, self.states.XYZZY) + self.assertEqual(self.states.state(item), self.states.XYZZY) + self.states.move(item, self.states.BAR) + self.assertEqual(self.states.state(item), self.states.BAR) + + + def test_item_move_from_new(self): + item = b'foo' + self.states.put(item) + self.assertEqual(self.states.state(item), self.states.NEW) + self.states.move(item, self.states.XYZZY) + self.assertEqual(self.states.state(item), self.states.XYZZY) + + + def test_item_purge(self): + item = b'foo' + self.states.put(item, self.states.BAZ) + self.assertEqual(self.states.state(item), self.states.BAZ) + self.states.purge(item) + with self.assertRaises(StateItemNotFound): + self.states.state(item) + + + +if __name__ == '__main__': + unittest.main()