shep

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

commit d8766253544bff4b28615d72fb4af00f06ab6cd4
parent 89d1a6ee3a2eabae1f0d7b6bef524a4d12fd8ea7
Author: lash <dev@holbrook.no>
Date:   Sun,  6 Feb 2022 20:43:20 +0000

WIP add docstrings to state.py

Diffstat:
Mshep/state.py | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 112 insertions(+), 5 deletions(-)

diff --git a/shep/state.py b/shep/state.py @@ -8,7 +8,17 @@ from shep.error import ( class State: + """State is an in-memory bitmasked state store for key-value pairs, or even just keys alone. + A State is comprised of a number of atomic state bits, and zero or more aliases that represent unique combinations of these bits. + + The State object will enforce that duplicate states cannot exist. It will also enforce that all alias states are composed of valid atomic states. + + :param bits: Number of atomic states that this State object will represent (i.e. number of bits). + :type bits: int + :param logger: Standard library logging instance to output to + :type logger: logging.Logger + """ def __init__(self, bits, logger=None): self.__bits = bits self.__limit = (1 << bits) - 1 @@ -21,6 +31,7 @@ class State: self.__contents = {} + # return true if v is a single-bit state def __is_pure(self, v): if v == 0: return True @@ -32,12 +43,14 @@ class State: return c == v + # validates a state name and return its canonical representation def __check_name_valid(self, k): if not k.isalpha(): raise ValueError('only alpha') return k.upper() + # enforces name validity, aswell as name uniqueness def __check_name(self, k): k = self.__check_name_valid(k) @@ -49,6 +62,7 @@ class State: return k + # enforces state value validity and uniqueness def __check_valid(self, v): v = self.__check_value_typ(v) if self.__reverse.get(v): @@ -56,22 +70,26 @@ class State: return v + # enforces state value within bit limit of instantiation def __check_limit(self, v): if v > self.__limit: raise OverflowError(v) return v + # enforces state value validity, uniqueness and value limit def __check_value(self, v): v = self.__check_valid(v) self.__check_limit(v) return v + # enforces state value validity def __check_value_typ(self, v): return int(v) + # enforces state value validity within the currently registered states (number of add calls vs number of bits in instantiation). def __check_value_cursor(self, v): v = self.__check_value_typ(v) if v > 1 << self.__c: @@ -79,17 +97,20 @@ class State: return v + # set a bit for state of the given key def __set(self, k, v): setattr(self, k, v) self.__reverse[v] = k self.__c += 1 + # check validity of key to register state for def __check_key(self, item): if self.__keys_reverse.get(item) != None: raise StateItemExists(item) + # adds a new key to the state store def __add_state_list(self, state, item): if self.__keys.get(state) == None: self.__keys[state] = [] @@ -97,6 +118,9 @@ class State: self.__keys_reverse[item] = state + # Get index of a key for a given state. + # A key should only ever exist in one state. + # A failed lookup should indicate a mistake on the caller part, (it may also indicate corruption, but probanbly impossible to tell the difference) def __state_list_index(self, item, state_list): idx = -1 try: @@ -110,6 +134,11 @@ class State: return idx + # Add a state to the store. + # + # :param k: State name + # :type k: str + # :raises shep.error.StateExists: State name is already registered def add(self, k): v = 1 << self.__c k = self.__check_name(k) @@ -117,6 +146,16 @@ class State: self.__set(k, v) + # Add an alias for a combination of states in the store. + # + # State aggregates may be provided as comma separated values or as a single (or'd) integer value. + #| + # :param k: Alias name + # :type k: str + # :param *args: One or more states to aggregate for this alias. + # :type *args: int or list of ints + # :raises StateInvalid: Attempt to create alias for one or more atomic states that do not exist. + # :raises ValueError: Attempt to use bit value as alias def alias(self, k, *args): k = self.__check_name(k) v = 0 @@ -128,6 +167,10 @@ class State: self.__set(k, v) + # Return list of all unique atomic and alias states. + # + # :rtype: list of ints + # :return: states def all(self): l = [] for k in dir(self): @@ -140,6 +183,13 @@ class State: return l + # Retrieve that string representation of the state attribute represented by the given state integer value. + # + # :param v: State integer + # :type v: int + # :raises StateInvalid: State corresponding to given integer not found + # :rtype: str + # :return: State name def name(self, v): if v == None or v == 0: return 'NEW' @@ -149,11 +199,30 @@ class State: return k + # Retrieve the real state integer value corresponding to an attribute name. + # + # :param k: Attribute name + # :type k: str + # :raises ValueError: Invalid attribute name + # :raises AttributeError: Attribute not found + # :rtype: int + # :return: Numeric state value def from_name(self, k): k = self.__check_name_valid(k) return getattr(self, k) + # Match against all stored states. + # + # If pure is set, only match against the single atomic state will be returned. + # + # :param v: Integer state to match + # :type v: int + # :param pure: Match only pure states + # :type pure: bool + # :raises KeyError: Unknown state + # :rtype: tuple + # :return: 0: Alias that input resolves to, 1: list of atomic states that matches the state def match(self, v, pure=False): alias = None if not pure: @@ -172,8 +241,24 @@ class State: return (alias, r,) - - def put(self, key, state=None, contents=None, force=False): + + # Add a key to an existing state. + # + # If no state it specified, the default state attribute "NEW" will be used. + # + # Contents may be supplied as value to pair with the given key. Contents may be changed later by calling the `replace` method. + # + # :param key: Content key to add + # :type key: str + # :param state: Initial state for the put. If not given, initial state will be NEW + # :type state: int + # :param contents: Contents to associate with key. A valie of None should be recognized as an undefined value as opposed to a zero-length value throughout any backend + # :type contents: str + # :raises StateItemExists: Content key has already been added + # :raises StateInvalid: Given state has not been registered + # :rtype: integer + # :return: Resulting state that key is put under (should match the input state) + def put(self, key, state=None, contents=None): if state == None: state = self.NEW elif self.__reverse.get(state) == None: @@ -190,6 +275,16 @@ class State: return state + # Move a given content key from one state to another. + # + # :param key: Key to move + # :type key: str + # :param to_state: Numeric state to move to (may be atomic or alias) + # :type to_state: integer + # :raises StateItemNotFound: Given key has not been registered + # :raises StateInvalid: Given state has not been registered + # :rtype: integer + # :return: Resulting state from move (should match the state given as input) def move(self, key, to_state): current_state = self.__keys_reverse.get(key) if current_state == None: @@ -202,6 +297,7 @@ class State: return self.__move(key, current_state, to_state) + # implementation for state move that ensures integrity of keys and states. def __move(self, key, from_state, to_state): current_state_list = self.__keys.get(from_state) if current_state_list == None: @@ -217,9 +313,20 @@ class State: current_state_list.pop(idx) return to_state - - - def set(self, key, or_state, content=None): + + + # Set a partial state bit. May result in an alias state being triggered. + # + # :param key: Content key to modify state for + # :type key: str + # :param or_state: Atomic stat to add + # :type or_state: int + # :raises ValueError: State is not a single bit state + # :raises StateItemNotFound: Content key is not registered + # :raises StateInvalid: Resulting state after addition of atomic state is unknown + # :rtype: int + # :returns: Resulting state + def set(self, key, or_state): if not self.__is_pure(or_state): raise ValueError('can only apply using single bit states')