Compare commits
7 Commits
0.0.19-dev
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3039595d40
|
||
|
|
d074174f37
|
||
|
|
7937cadaef
|
||
|
|
5cc0af80d6
|
||
|
|
dbb2280a03
|
||
|
|
1349741a48
|
||
|
|
d876625354
|
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = shep
|
||||
version = 0.0.19
|
||||
version = 0.1.0
|
||||
description = Multi-state key stores using bit masks
|
||||
author = Louis Holbrook
|
||||
author_email = dev@holbrook.no
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
class StateExists(Exception):
|
||||
"""Attempt to add state that already exists.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class StateInvalid(Exception):
|
||||
"""Attempt to operate on or move to a state that does not exist.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class StateItemExists(Exception):
|
||||
"""A content key attempted added that already exists.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class StateItemNotFound(Exception):
|
||||
"""A content key attempted read that does not exist.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class StateCorruptionError(RuntimeError):
|
||||
"""An irrecoverable discrepancy between persisted state and memory state has occurred.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -4,6 +4,15 @@ from .error import StateItemExists
|
||||
|
||||
|
||||
class PersistedState(State):
|
||||
"""Adapter for persisting state changes and synchronising states between memory and persisted backend.
|
||||
|
||||
:param factory: A function capable of returning a persisted store from a single path argument.
|
||||
:type factory: function
|
||||
:param bits: Number of pure states. Passed to the superclass.
|
||||
:type bits: int
|
||||
:param logger: Logger to capture logging output, or None for no logging.
|
||||
:type logger: object
|
||||
"""
|
||||
|
||||
def __init__(self, factory, bits, logger=None):
|
||||
super(PersistedState, self).__init__(bits, logger=logger)
|
||||
@@ -11,12 +20,17 @@ class PersistedState(State):
|
||||
self.__stores = {}
|
||||
|
||||
|
||||
# Create state store container if missing.
|
||||
def __ensure_store(self, k):
|
||||
if self.__stores.get(k) == None:
|
||||
self.__stores[k] = self.__store_factory(k)
|
||||
|
||||
|
||||
def put(self, key, contents=None, state=None):
|
||||
"""Persist a key or key/content pair.
|
||||
|
||||
See shep.state.State.put
|
||||
"""
|
||||
to_state = super(PersistedState, self).put(key, state=state, contents=contents)
|
||||
|
||||
k = self.name(to_state)
|
||||
@@ -26,6 +40,10 @@ class PersistedState(State):
|
||||
|
||||
|
||||
def set(self, key, or_state):
|
||||
"""Persist a new state for a key or key/content.
|
||||
|
||||
See shep.state.State.set
|
||||
"""
|
||||
from_state = self.state(key)
|
||||
k_from = self.name(from_state)
|
||||
|
||||
@@ -41,6 +59,10 @@ class PersistedState(State):
|
||||
|
||||
|
||||
def unset(self, key, not_state):
|
||||
"""Persist a new state for a key or key/content.
|
||||
|
||||
See shep.state.State.unset
|
||||
"""
|
||||
from_state = self.state(key)
|
||||
k_from = self.name(from_state)
|
||||
|
||||
@@ -57,11 +79,16 @@ class PersistedState(State):
|
||||
|
||||
|
||||
def move(self, key, to_state):
|
||||
"""Persist a new state for a key or key/content.
|
||||
|
||||
See shep.state.State.move
|
||||
"""
|
||||
from_state = self.state(key)
|
||||
to_state = super(PersistedState, self).move(key, to_state)
|
||||
return self.__movestore(key, from_state, to_state)
|
||||
|
||||
|
||||
# common procedure for safely moving a persisted resource from one state to another.
|
||||
def __movestore(self, key, from_state, to_state):
|
||||
k_from = self.name(from_state)
|
||||
k_to = self.name(to_state)
|
||||
@@ -76,6 +103,13 @@ class PersistedState(State):
|
||||
|
||||
|
||||
def sync(self, state):
|
||||
"""Reload resources for a single state in memory from the persisted state store.
|
||||
|
||||
:param state: State to load
|
||||
:type state: int
|
||||
:raises StateItemExists: A content key is already recorded with a different state in memory than in persisted store.
|
||||
# :todo: if sync state is none, sync all
|
||||
"""
|
||||
k = self.name(state)
|
||||
|
||||
self.__ensure_store(k)
|
||||
@@ -89,6 +123,12 @@ class PersistedState(State):
|
||||
|
||||
|
||||
def list(self, state):
|
||||
"""List all content keys for a particular state.
|
||||
|
||||
This method will return from memory, and will not sync the persisted state first.
|
||||
|
||||
See shep.state.State.list
|
||||
"""
|
||||
k = self.name(state)
|
||||
self.__ensure_store(k)
|
||||
#return self.__stores[k].list(state)
|
||||
@@ -96,18 +136,38 @@ class PersistedState(State):
|
||||
|
||||
|
||||
def path(self, state, key=None):
|
||||
"""Return a file path or URL pointing to the persisted state.
|
||||
|
||||
If the key is omitted, the URL to the state item's container must be returned, and None if no such container exists.
|
||||
|
||||
:param state: State to locate
|
||||
:type state: int
|
||||
:param key: Content key to locate
|
||||
:type key: str
|
||||
:rtype: str
|
||||
:returns: Locator pointng to persisted state
|
||||
:todo: rename to "location"
|
||||
"""
|
||||
k = self.name(state)
|
||||
self.__ensure_store(k)
|
||||
return self.__stores[k].path(key=key)
|
||||
return self.__stores[k].path(k=key)
|
||||
|
||||
|
||||
def next(self, key=None):
|
||||
"""Advance and persist to the next pure state.
|
||||
|
||||
See shep.state.State.next
|
||||
"""
|
||||
from_state = self.state(key)
|
||||
to_state = super(PersistedState, self).next(key)
|
||||
return self.__movestore(key, from_state, to_state)
|
||||
|
||||
|
||||
def replace(self, key, contents):
|
||||
"""Replace contents associated by content key.
|
||||
|
||||
See shep.state.State.replace
|
||||
"""
|
||||
super(PersistedState, self).replace(key, contents)
|
||||
state = self.state(key)
|
||||
k = self.name(state)
|
||||
|
||||
210
shep/state.py
210
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] = []
|
||||
@@ -98,6 +119,10 @@ class State:
|
||||
|
||||
|
||||
def __state_list_index(self, item, state_list):
|
||||
"""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)
|
||||
"""
|
||||
idx = -1
|
||||
try:
|
||||
idx = state_list.index(item)
|
||||
@@ -111,6 +136,12 @@ class State:
|
||||
|
||||
|
||||
def add(self, k):
|
||||
"""Add a state to the store.
|
||||
|
||||
:param k: State name
|
||||
:type k: str
|
||||
:raises shep.error.StateExists: State name is already registered
|
||||
"""
|
||||
v = 1 << self.__c
|
||||
k = self.__check_name(k)
|
||||
v = self.__check_value(v)
|
||||
@@ -118,6 +149,17 @@ class State:
|
||||
|
||||
|
||||
def alias(self, k, *args):
|
||||
"""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
|
||||
"""
|
||||
k = self.__check_name(k)
|
||||
v = 0
|
||||
for a in args:
|
||||
@@ -129,6 +171,11 @@ class State:
|
||||
|
||||
|
||||
def all(self):
|
||||
"""Return list of all unique atomic and alias states.
|
||||
|
||||
:rtype: list of ints
|
||||
:return: states
|
||||
"""
|
||||
l = []
|
||||
for k in dir(self):
|
||||
if k[0] == '_':
|
||||
@@ -141,6 +188,14 @@ class State:
|
||||
|
||||
|
||||
def name(self, v):
|
||||
"""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
|
||||
"""
|
||||
if v == None or v == 0:
|
||||
return 'NEW'
|
||||
k = self.__reverse.get(v)
|
||||
@@ -150,11 +205,32 @@ class State:
|
||||
|
||||
|
||||
def from_name(self, 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
|
||||
"""
|
||||
k = self.__check_name_valid(k)
|
||||
return getattr(self, k)
|
||||
|
||||
|
||||
def match(self, v, pure=False):
|
||||
"""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
|
||||
"""
|
||||
alias = None
|
||||
if not pure:
|
||||
alias = self.__reverse.get(v)
|
||||
@@ -172,17 +248,30 @@ class State:
|
||||
|
||||
return (alias, r,)
|
||||
|
||||
|
||||
def put(self, key, state=None, contents=None, force=False):
|
||||
|
||||
def put(self, key, state=None, contents=None):
|
||||
"""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)
|
||||
"""
|
||||
if state == None:
|
||||
state = self.NEW
|
||||
elif self.__reverse.get(state) == None:
|
||||
raise StateInvalid(state)
|
||||
try:
|
||||
self.__check_key(key)
|
||||
except StateItemExists as e:
|
||||
if not force:
|
||||
raise(e)
|
||||
self.__check_key(key)
|
||||
self.__add_state_list(state, key)
|
||||
if contents != None:
|
||||
self.__contents[key] = contents
|
||||
@@ -191,6 +280,17 @@ class State:
|
||||
|
||||
|
||||
def move(self, key, to_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)
|
||||
"""
|
||||
current_state = self.__keys_reverse.get(key)
|
||||
if current_state == None:
|
||||
raise StateItemNotFound(key)
|
||||
@@ -202,6 +302,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 +318,21 @@ class State:
|
||||
current_state_list.pop(idx)
|
||||
|
||||
return to_state
|
||||
|
||||
|
||||
|
||||
def set(self, key, or_state, content=None):
|
||||
def set(self, key, or_state):
|
||||
"""Move to an alias state by setting a single bit.
|
||||
|
||||
: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
|
||||
"""
|
||||
if not self.__is_pure(or_state):
|
||||
raise ValueError('can only apply using single bit states')
|
||||
|
||||
@@ -236,6 +349,20 @@ class State:
|
||||
|
||||
|
||||
def unset(self, key, not_state):
|
||||
"""Unset a single bit, moving to a pure or alias state.
|
||||
|
||||
The resulting state cannot be NEW (0).
|
||||
|
||||
: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, or attempts to revert to NEW
|
||||
:raises StateItemNotFound: Content key is not registered
|
||||
:raises StateInvalid: Resulting state after addition of atomic state is unknown
|
||||
:rtype: int
|
||||
:returns: Resulting state
|
||||
"""
|
||||
if not self.__is_pure(not_state):
|
||||
raise ValueError('can only apply using single bit states')
|
||||
|
||||
@@ -247,6 +374,9 @@ class State:
|
||||
if to_state == current_state:
|
||||
raise ValueError('invalid change for state {}: {}'.format(key, not_state))
|
||||
|
||||
if to_state == self.NEW:
|
||||
raise ValueError('State {} for {} cannot be reverted to NEW'.format(current_state, key))
|
||||
|
||||
new_state = self.__reverse.get(to_state)
|
||||
if new_state == None:
|
||||
raise StateInvalid('resulting to state is unknown: {}'.format(to_state))
|
||||
@@ -255,17 +385,39 @@ class State:
|
||||
|
||||
|
||||
def state(self, key):
|
||||
"""Return the current numeric state for the given content key.
|
||||
|
||||
:param key: Key to return content for
|
||||
:type key: str
|
||||
:raises StateItemNotFound: Content key is unknown
|
||||
:rtype: int
|
||||
:returns: State
|
||||
"""
|
||||
state = self.__keys_reverse.get(key)
|
||||
if state == None:
|
||||
raise StateItemNotFound(key)
|
||||
return state
|
||||
|
||||
|
||||
def get(self, key=None):
|
||||
def get(self, key):
|
||||
"""Retrieve the content for a content key.
|
||||
|
||||
:param key: Content key to retrieve content for
|
||||
:type key: str
|
||||
:rtype: any
|
||||
:returns: Content
|
||||
"""
|
||||
return self.__contents.get(key)
|
||||
|
||||
|
||||
def list(self, state):
|
||||
"""List all content keys matching a state.
|
||||
|
||||
:param state: State to match
|
||||
:type state: int
|
||||
:rtype: list of str
|
||||
:returns: Matching content keys
|
||||
"""
|
||||
try:
|
||||
return self.__keys[state]
|
||||
except KeyError:
|
||||
@@ -273,14 +425,35 @@ class State:
|
||||
|
||||
|
||||
def sync(self, state):
|
||||
"""Noop method for interface implementation providing sync to backend.
|
||||
|
||||
:param state: State to sync.
|
||||
:type state:
|
||||
:todo: (for higher level implementer) if sync state is none, sync all
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def path(self, state, key=None):
|
||||
"""In the memory-only class no persisted state is used, and this will return None.
|
||||
|
||||
See shep.persist.PersistedState.path for more information.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
def peek(self, key):
|
||||
"""Return the next pure state.
|
||||
|
||||
Will return the same result as the method next, but without advancing to the new state.
|
||||
|
||||
:param key: Content key to inspect state for
|
||||
:type key: str
|
||||
:raises StateItemNotFound: Unknown content key
|
||||
:raises StateInvalid: Attempt to advance from an alias state, OR beyond the last known pure state.
|
||||
:rtype: int
|
||||
:returns: Next state
|
||||
"""
|
||||
state = self.__keys_reverse.get(key)
|
||||
if state == None:
|
||||
raise StateItemNotFound(key)
|
||||
@@ -298,11 +471,28 @@ class State:
|
||||
|
||||
|
||||
def next(self, key):
|
||||
"""Advance to the next pure state.
|
||||
|
||||
:param key: Content key to inspect state for
|
||||
:type key: str
|
||||
:raises StateItemNotFound: Unknown content key
|
||||
:raises StateInvalid: Attempt to advance from an alias state, OR beyond the last known pure state.
|
||||
:rtype: int
|
||||
:returns: Next state
|
||||
"""
|
||||
from_state = self.state(key)
|
||||
new_state = self.peek(key)
|
||||
return self.__move(key, from_state, new_state)
|
||||
|
||||
|
||||
def replace(self, key, contents):
|
||||
"""Replace contents associated by content key.
|
||||
|
||||
:param key: Content key to replace for
|
||||
:type key: str
|
||||
:param contents: New contents
|
||||
:type contents: any
|
||||
:raises KeyError: Unknown content key
|
||||
"""
|
||||
self.state(key)
|
||||
self.__contents[key] = contents
|
||||
|
||||
@@ -3,13 +3,24 @@ import os
|
||||
|
||||
|
||||
class SimpleFileStore:
|
||||
"""Filesystem store of contents for state, with one directory per state.
|
||||
|
||||
:param path: Filesystem base path for all state directory
|
||||
:type path: str
|
||||
"""
|
||||
def __init__(self, path):
|
||||
self.__path = path
|
||||
os.makedirs(self.__path, exist_ok=True)
|
||||
|
||||
|
||||
def add(self, k, contents=None):
|
||||
"""Add a new key and optional contents
|
||||
|
||||
:param k: Content key to add
|
||||
:type k: str
|
||||
:param contents: Optional contents to assign for content key
|
||||
:type contents: any
|
||||
"""
|
||||
fp = os.path.join(self.__path, k)
|
||||
if contents == None:
|
||||
contents = ''
|
||||
@@ -20,11 +31,25 @@ class SimpleFileStore:
|
||||
|
||||
|
||||
def remove(self, k):
|
||||
"""Remove a content key from a state.
|
||||
|
||||
:param k: Content key to remove from the state
|
||||
:type k: str
|
||||
:raises FileNotFoundError: Content key does not exist in the state
|
||||
"""
|
||||
fp = os.path.join(self.__path, k)
|
||||
os.unlink(fp)
|
||||
|
||||
|
||||
|
||||
def get(self, k):
|
||||
"""Retrieve the content for the given content key.
|
||||
|
||||
:param k: Content key to retrieve content for
|
||||
:type k: str
|
||||
:raises FileNotFoundError: Content key does not exist for the state
|
||||
:rtype: any
|
||||
:return: Contents
|
||||
"""
|
||||
fp = os.path.join(self.__path, k)
|
||||
f = open(fp, 'r')
|
||||
r = f.read()
|
||||
@@ -33,6 +58,11 @@ class SimpleFileStore:
|
||||
|
||||
|
||||
def list(self):
|
||||
"""List all content keys persisted for the state.
|
||||
|
||||
:rtype: list of str
|
||||
:return: Content keys in state
|
||||
"""
|
||||
files = []
|
||||
for p in os.listdir(self.__path):
|
||||
fp = os.path.join(self.__path, p)
|
||||
@@ -45,14 +75,28 @@ class SimpleFileStore:
|
||||
return files
|
||||
|
||||
|
||||
def path(self, key=None):
|
||||
if key == None:
|
||||
def path(self, k=None):
|
||||
"""Return filesystem path for persisted state or state item.
|
||||
|
||||
:param k: If given, will return filesystem path to specified content key
|
||||
:type k: str
|
||||
:rtype: str
|
||||
:return: File path
|
||||
"""
|
||||
if k == None:
|
||||
return self.__path
|
||||
return os.path.join(self.__path, key)
|
||||
return os.path.join(self.__path, k)
|
||||
|
||||
|
||||
def replace(self, key, contents):
|
||||
fp = os.path.join(self.__path, key)
|
||||
def replace(self, k, contents):
|
||||
"""Replace persisted content for persisted content key.
|
||||
|
||||
:param k: Content key to replace contents for
|
||||
:type k: str
|
||||
:param contents: Contents
|
||||
:type contents: any
|
||||
"""
|
||||
fp = os.path.join(self.__path, k)
|
||||
os.stat(fp)
|
||||
f = open(fp, 'w')
|
||||
r = f.write(contents)
|
||||
@@ -60,12 +104,23 @@ class SimpleFileStore:
|
||||
|
||||
|
||||
class SimpleFileStoreFactory:
|
||||
"""Provide a method to instantiate SimpleFileStore instances that provide persistence for individual states.
|
||||
|
||||
:param path: Filesystem path as base path for states
|
||||
:type path: str
|
||||
"""
|
||||
def __init__(self, path):
|
||||
self.__path = path
|
||||
|
||||
|
||||
def add(self, k):
|
||||
"""Create a new SimpleFileStore for a state.
|
||||
|
||||
:param k: Identifier for the state
|
||||
:type k: str
|
||||
:rtype: SimpleFileStore
|
||||
:return: A filesystem persistence instance with the given identifier as subdirectory
|
||||
"""
|
||||
k = str(k)
|
||||
store_path = os.path.join(self.__path, k)
|
||||
return SimpleFileStore(store_path)
|
||||
|
||||
Reference in New Issue
Block a user