diff --git a/src/energenie/KVS.py b/src/energenie/KVS.py index 92b3339..9be72fc 100644 --- a/src/energenie/KVS.py +++ b/src/energenie/KVS.py @@ -10,7 +10,7 @@ self.filename = filename self.store = {} - def load(self, filename=None, create_cb=None): + def load(self, filename=None, factory_cb=None): """Load the whole file into an in-memory cache""" # use new filename if provided, else use existing filename @@ -51,27 +51,38 @@ k,v = line.split("=", 1) obj[k] = v else: # is blank - self.process(command, key, obj) + self.process(command, key, obj, factory_cb) command = None is_cmd = True self.filename = filename # remember filename if it was provided - def process(self, command, key, obj): + def process(self, command, key, obj, factory_cb): """Process the temporary object""" m = getattr(self, command) #If command is not found? get AttributeError - that's fine - m(key, obj) + m(key, obj, factory_cb) - def ADD(self, key, obj): + def ADD(self, key, obj, factory=None): """Add a new item to the kvs""" - # The ADD command process the next type= parameter as the class name in context + # The ADD command processes the next type= parameter as the class name in context # all other parameters are read as strings and passed to class constructor as kwargs + + if factory != None: + print("*** calling factory to turn into a class instance") + type = obj["type"] + del obj["type"] # don't pass to constructor + obj = factory.get(type, **obj) + # If this fails, then this is an error, so just let it bubble out + else: + print("*** no factory configured, just storing kvp") + + print("*** object: %s" % obj) + # store kvp or class instance appropriately self.store[key] = obj - self.append(key, obj) @unimplemented - def IGN(self, key, obj=None): + def IGN(self, key, obj=None, factory=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 @@ -79,7 +90,7 @@ pass # There is nothing to do with this command @unimplemented - def DEL(self, key, obj=None): + def DEL(self, key, obj=None, factory=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. @@ -95,6 +106,7 @@ self.remove(key) # patches it to an IGN record self.store[key] = value + #TODO: If this fails, just add the kv pair map as the object obj = value.get_config() # will fail with AttributeError if this method does not exist self.append(key, obj) @@ -120,7 +132,6 @@ f.write("%s=%s\n" % (k, v)) f.write("\n") - @untested def remove(self, key): """Remove reference to this key in the file""" if self.filename == None: diff --git a/src/energenie/KVS_test.py b/src/energenie/KVS_test.py index 870138a..9254068 100644 --- a/src/energenie/KVS_test.py +++ b/src/energenie/KVS_test.py @@ -10,6 +10,7 @@ class TV(): def __init__(self, id): + print("Creating TV %s" % id) self.id = id def __repr__(self): @@ -178,22 +179,58 @@ #---- HERE ---- - @unimplemented - @test_1 - def test_ADD(self): - pass #TODO: do ADD records get added when parsing the file? + @test_0 + def test_ADD_nofactory(self): + #NOTE: This is an under the bonnet test of parsing an ADD record from the file - @unimplemented + # No factory callback provided, use ADD parse action + obj = { + "type": "MIHO005", + "id": 1234 + } + kvs = KVS(self.KVS_FILENAME) + kvs.ADD("tv1", obj) + + # expected result: object described as a kvp becomes a kvp in the store if no factory callback + print(kvs.store) + + @test_1 + def test_ADD_factory(self): + #NOTE: This is an under the bonnet test of parsing an ADD record from the file + obj = { + "type": "TV", + "id": 1234 + } + kvs = KVS(self.KVS_FILENAME) + + class FACTORY(): + @staticmethod + def get(name, **kwargs): + if name == "TV": return TV(**kwargs) + else: + raise ValueError("Unknown device name %s" % name) + + kvs.ADD("tv1", obj, FACTORY) + + #TODO all non type args need to be passed as the kwargs to the factory.get + + # expected result: object described as a kvp becomes a configured object instance in store + print(kvs.store) + + @test_0 def test_IGN(self): + #NOTE: This is an under the bonnet test of parsing an IGN record from the file pass #TODO: do IGN records get ignored when parsing the file? + # expected result: no change to the in memory data structures - @unimplemented @test_0 def test_DEL(self): + #NOTE: This is an under the bonnet test of parsing a DEL record from the file pass #TODO: do DEL records get processed when parsing the file? + # expected result: record is deleted from in memory store + # expected result: error if it was not in the store in the first place - @unimplemented @test_0 def test_load_process(self): """Load and process a file with lots of records in it""" @@ -203,6 +240,13 @@ pass #TODO +#TODO: Other tests - for integrating with the registry later +#pass in an object creator callback, should turn kvp into object instance +#when persisting, try to call get_config(), if it works, persist the kvp, +#if there is no get_config(), decide what to persist, if anything, +#or throw a NotPersistable error perhaps? +#Look to see if there is a pythonic way to do this, perhaps with one of +#the meta methods? if __name__ == "__main__": unittest.main()