diff --git a/src/energenie/KVS.py b/src/energenie/KVS.py new file mode 100644 index 0000000..f843890 --- /dev/null +++ b/src/energenie/KVS.py @@ -0,0 +1,132 @@ +# KVS.py 27/05/2016 D.J.Whale +# +# A generic key value store + +from lifecycle import * + +class KVSFile(): + """A persistent key value store""" + def __init__(self, filename=None): + self.filename = filename + self.store = {} + + @unimplemented + def load(self, factory): + """Load the whole file into an in-memory cache""" + # The 'factory' is a place to go to turn device type names into actual class instances + pass #TODO + # open file for read + # for each line read + # if in command mode + # if blank line, ignore it + # else not blank line + # split line, first word is command, second word is the key + # remember both + # change to data mode + # else in data mode + # if not blank line + # grab key=value + # add to temporary object + # else blank line + # process command,key,values + # now eof + # process command,key,values, if it command is not empty + # close file + + @unimplemented + def process(self, command, key, values): + """Process the temporary object""" + pass #TODO + # getattr method associated with the command name, error if no method + # pass the key,values to that method to let it be processed + + @unimplemented + def ADD(self, key, values): + """Add a new item to the kvs""" + # The ADD command process the next type= parameter as the class name in context + # all other parameters are read as strings and passed to class constructor as kwargs + pass #TODO + # add key=values to the in memory object store + # open file for append + # write ADD command with key + # for all keys in value + # write k=v + # close file + + @unimplemented + def IGN(self, key, values=None): + """Ignore the whole record""" + # The IGN command is the same length as ADD, allowing a seek/write to change any + # command into IGN without changing the file size, effectively patching the file + # so that the record is deleted. + pass # There is nothing to do with this command + + @unimplemented + def DEL(self, key, values=None): + """Delete the key from the store""" + # The DEL command deletes the rec from the store. + # This is useful to build temporary objects and delete them later. + # There is no need to write this to the file copy, we're processing the file + pass #TODO + # find key in object store, delete it + + @untested + def __getitem__(self, key): + return self.store[key] + + @untested + def __setitem__(self, key, value): + self.store[key] = value + self.append(key, value) + + @untested + def __delitem__(self, key): + del self.store[key] + self.remove(key) + + @untested + def keys(self): + return self.store.keys() + + @untested + def size(self): + return len(self.store) + + def append(self, key, values): + """Append a new record to the persistent file""" + with open(self.filename, 'a') as f: + f.write("ADD %s\n" % key) + for k in values: + v = values[k] + f.write("%s=%s\n" % (k, v)) + f.write("\n") + + + @unimplemented + def remove(self, key): + """Remove reference to this key in the file, and remove from in memory store""" + pass #TODO + # open file for read write + # search line at a time, process each command + # when we find the command 'ADD key' + # reseek to start of line + # write overwrite ADD with IGN + # keep going in case of duplicates + # close file + + @unimplemented + def rewrite(self): + """Rewrite the whole in memory cache over the top of the external file""" + # useful if you have updated the in memory copy only and want to completely regenerate + pass #TODO + # create file new, for write only + # for all objects in the store by key + # get value + # write ADD command key + # for all values + # write k=v + # write blank line + # close file + +# END + diff --git a/src/energenie/KVS_test.py b/src/energenie/KVS_test.py new file mode 100644 index 0000000..1d190f7 --- /dev/null +++ b/src/energenie/KVS_test.py @@ -0,0 +1,34 @@ +# KVS_test.py 27/05/2016 D.J.Whale +# +# Tester for Key Value Store + +import unittest +from lifecycle import * + + +class TestKVSMemory(unittest.TestCase): + pass #TODO + + # create blank + # add + # get + # delete + # get size + # get keys + +class TestKVSPersisted(unittest.TestCase): + pass #TODO + + # persist non persisted KVS + # create from file and load + # add (does persistent version change) + # delete (does persistent version change) + # reload with IGN records + # reload with DEL records + # rewrite to a new file + + +if __name__ == "__main__": + unittest.main() + +# END \ No newline at end of file diff --git a/src/energenie/Registry.py b/src/energenie/Registry.py index 094cfa7..6cfa5fb 100644 --- a/src/energenie/Registry.py +++ b/src/energenie/Registry.py @@ -16,6 +16,8 @@ from . import Devices from . import OpenThings +from KVS import KVS + directory = {} @@ -63,134 +65,6 @@ return directory[sensor_id] -#----- GENERIC KEY VALUE STORE ------------------------------------------------ - -class KVS(): - """A persistent key value store""" - def __init__(self, filename=None): - self.filename = filename - self.store = {} - - @unimplemented - def load(self, factory): - """Load the whole file into an in-memory cache""" - # The 'factory' is a place to go to turn device type names into actual class instances - pass #TODO - # open file for read - # for each line read - # if in command mode - # if blank line, ignore it - # else not blank line - # split line, first word is command, second word is the key - # remember both - # change to data mode - # else in data mode - # if not blank line - # grab key=value - # add to temporary object - # else blank line - # process command,key,values - # now eof - # process command,key,values, if it command is not empty - # close file - - @unimplemented - def process(self, command, key, values): - """Process the temporary object""" - pass #TODO - # getattr method associated with the command name, error if no method - # pass the key,values to that method to let it be processed - - @unimplemented - def ADD(self, key, values): - """Add a new item to the kvs""" - # The ADD command process the next type= parameter as the class name in context - # all other parameters are read as strings and passed to class constructor as kwargs - pass #TODO - # add key=values to the in memory object store - # open file for append - # write ADD command with key - # for all keys in value - # write k=v - # close file - - @unimplemented - def IGN(self, key, values=None): - """Ignore the whole record""" - # The IGN command is the same length as ADD, allowing a seek/write to change any - # command into IGN without changing the file size, effectively patching the file - # so that the record is deleted. - pass # There is nothing to do with this command - - @unimplemented - def DEL(self, key, values=None): - """Delete the key from the store""" - # The DEL command deletes the rec from the store. - # This is useful to build temporary objects and delete them later. - # There is no need to write this to the file copy, we're processing the file - pass #TODO - # find key in object store, delete it - - @untested - def __getitem__(self, key): - return self.store[key] - - @untested - def __setitem__(self, key, value): - self.store[key] = value - self.append(key, value) - - @untested - def __delitem__(self, key): - del self.store[key] - self.remove(key) - - @untested - def keys(self): - return self.store.keys() - - @untested - def size(self): - return len(self.store) - - @untested - def append(self, key, values): - print("####HERE") - print(values, type(values)) - """Append a new record to the persistent file""" - with open(self.filename, 'a') as f: - f.write("ADD %s\n" % key) - for k in values: - v = values[k] - f.write("%s=%s\n" % (k, v)) - f.write("\n") - - - @unimplemented - def remove(self, key): - """Remove reference to this key in the file, and remove from in memory store""" - pass #TODO - # open file for read write - # search line at a time, process each command - # when we find the command 'ADD key' - # reseek to start of line - # write overwrite ADD with IGN - # keep going in case of duplicates - # close file - - @unimplemented - def rewrite(self): - """Rewrite the whole in memory cache over the top of the external file""" - # useful if you have updated the in memory copy only and want to completely regenerate - pass #TODO - # create file new, for write only - # for all objects in the store by key - # get value - # write ADD command key - # for all values - # write k=v - # write blank line - # close file #----- NEW DEVICE REGISTRY ---------------------------------------------------- diff --git a/src/energenie/Registry_test.py b/src/energenie/Registry_test.py index dd6b77c..f74a1b5 100644 --- a/src/energenie/Registry_test.py +++ b/src/energenie/Registry_test.py @@ -15,34 +15,55 @@ radio.DEBUG=True -class TestRegistry(unittest.TestCase): +REGISTRY_KVS = "registry.kvs" - @test_1 - def test_create(self): - import os - os.unlink('registry.kvs') - registry = DeviceRegistry("registry.kvs") +def remove_file(filename): + import os + os.unlink(filename) - # add some devices to the registry, it should auto update the file - registry.add(Devices.MIHO005(device_id=0x68b), "tv") - #TODO: Need to have a way to get the persistent summary of a device class - #Perhaps in Device(), LegacyDevice() and MiHomeDevice() it does this for us - #and returns a map we can just persist and create from later? - registry.add(Devices.ENER002(device_id=(0xC8C8C, 1)), "fan") - - # see what the file looks like - with open(registry.DEFAULT_FILENAME) as f: +def show_file(filename): + """Show the contents of a file on screen""" + with open(filename) as f: for l in f.readlines(): l = l.strip() # remove nl print(l) + +class TestRegistry(unittest.TestCase): + @test_0 + def test_create(self): + remove_file(REGISTRY_KVS) + registry = DeviceRegistry(REGISTRY_KVS) + + # add some devices to the registry, it should auto update the file + registry.add(Devices.MIHO005(device_id=0x68b), "tv") + registry.add(Devices.ENER002(device_id=(0xC8C8C, 1)), "fan") + + # see what the file looks like + show_file(registry.DEFAULT_FILENAME) + + @test_1 def test_load(self): - pass #TODO - # load from a persisted registry - # list the registry in memory - # see that each item is instantiated and has a route + # create a registry file + remove_file(REGISTRY_KVS) + registry = DeviceRegistry(REGISTRY_KVS) + registry.add(Devices.MIHO005(device_id=0x68b), "tv") + registry.add(Devices.ENER002(device_id=(0xC8C8C, 1)), "fan") + + # clear the in memory registry + registry = None + + # create and load from file + registry = DeviceRegistry() + registry.load_from(REGISTRY_KVS) + + # dump the registry state + registry.list() + + #TODO: What about receive route testing?? + @test_0 def test_load_into(self):