def get_database_as_json(self, path_string, database='config', separator='/', pretty=False): answer = {'root': {}} path = decode_path_string(path_string, separator=separator) self.log.trace('GET_CURLY_VIEW %s' % (self.map)) for data in self.map: if database == 'config': self.log.trace('GET_CURLY_VIEW_CFG %s' % (data)) db = self.map[data]['config'] else: self.log.trace('GET_CURLY_VIEW_OPER %s' % (data)) db = self.map[data]['oper'] path_to_sub_datastore = decode_path_string(data, separator='/') try: data_from_sub_datastore = dpath.util.get( db.db, path_to_sub_datastore) dpath.util.new(answer, path_to_sub_datastore, data_from_sub_datastore) except KeyError: pass if pretty: if len(answer['root']) == 0: return 'Database is blank!' return json.dumps(answer['root'], indent=4, sort_keys=True) return json.dumps(answer, indent=4, sort_keys=True)
def test_decode_path_string(self): result = decode_path_string('/x/1/2/3{abc}', separator='/') self.assertEqual(['root', 'x', '1', '2', '3', '{abc}'], result) result = decode_path_string('/x/1/2/3{abc}/x{def}', separator='/') self.assertEqual(['root', 'x', '1', '2', '3', '{abc}', 'x', '{def}'], result)
def set_from_string(self, full_string, database='config', command=''): """ A convenience function to split apart the path, command and value and then call the set function. """ value = decode_path_string(full_string[len(command):], get_index=-1) path = decode_path_string(full_string[:-len(value) - 1]) self.set(path, value, database, separator=' ')
def _add_to_list(self, db, node_type, path_string, list_keys, separator=' '): path = decode_path_string(path_string, separator) list_element = dpath.util.get(self.schema, path) if not len(list_keys) == len( list_element['__listelement']['__schema']['__keys']): raise PyConfHoardWrongKeys( path, list_element['__listelement']['__schema']['__keys']) # string_composite_key = '{' string_composite_key = '' lk = 0 for list_key in list_element['__listelement']['__schema']['__keys']: string_composite_key = string_composite_key + list_keys[lk] + ' ' lk = lk + 1 # string_composite_key = string_composite_key[:-1] + '}' string_composite_key = string_composite_key[:-1] path.append('{' + string_composite_key + '}') lk = 0 for list_key in list_element['__listelement']['__schema']['__keys']: path.append(list_key) self.keyval[path_string + '{' + list_keys[lk] + '}/' + list_key] = list_keys[lk] dpath.util.new(db, path, list_keys[lk]) path.pop() lk = lk + 1 self._create_new_list_element_in_schema(path, string_composite_key)
def set(self, path, set_val, separator=' '): """Set the value of a leaf node. This function requires a decoded path as a string e.g. ['root', 'brewhouse', 'temperature', 'mash', 'setpoint'] -> 65 TODO: This needs to validate against the schema! """ path_string = convert_path_to_slash_string(path) self.log.trace('%s -> %s (separator %s)', path_string, set_val, separator) node_type = self.get_type(path_string, '/') self.log.trace('%s VALIDATE %s' % (path_string, node_type)) set_val = self.validate_against_schema({'__schema': node_type}, set_val, path_string) self.keyval[path_string] = set_val regex = re.compile("{([A-Za-z0-9]*)}\/?") path_string = regex.sub('/{\g<1>}/', path_string) self.log.trace('updated patH_string inside set %s' % (path_string)) path = decode_path_string(path_string, separator) self.log.trace('%s %s' % (path, set_val)) self.log.trace('%s' % (self.db)) dpath.util.new(self.db, path, set_val, separator='/') print(self.db) print(self.keyval)
def list(self, path_string, separator=' '): """ Shows structure of the databas """ self.log.trace('%s LIST' % (path_string)) path = decode_path_string(path_string, separator) return fetch_keys_from_path(self.schema, path)
def register(self, path_string, appname, readonly=False, skip_schema_load=False): """ This method will registers a configuration/oper datastores at a specifc part of the database. """ if str(path_string) in self.map: raise PyConfHoardPathAlreadyRegistered(path_string) path = decode_path_string(path_string, separator='/') thisconfig = PyConfHoardDatastore() thisoper = PyConfHoardDatastore() thisconfig.id = appname thisconfig.config = True thisoper.id = appname thisoper.config = False if not skip_schema_load: thisconfig.load_blank_schema(self.config_schema) thisoper.load_blank_schema(self.oper_schema) dpath.util.new(self.config.db, path, thisconfig) dpath.util.new(self.oper.db, path, thisoper) self.map[path_string] = {'config': thisconfig, 'oper': thisoper} thisconfig.readonly = False thisoper.readonly = False return (thisconfig, thisoper)
def list(self, path_string, database=None, separator=' '): try: data = self._lookup_datastore(path_string, separator=separator) except PyConfHoardDataPathNotRegistered as err: if database == 'config': data = self.config else: data = self.oper self.log.trace('Using instance %s for list operation' % (data)) if isinstance(path_string, list): path = path_string else: path = decode_path_string(path_string, separator=separator) if database is None or database == 'config': try: return self.config.list(path_string, separator) except: pass if database is None or database == 'oper': try: return self.oper.list(path_string, separator) except: pass raise PyConfHoardDataPathDoesNotExist(path_string)
def _auto_complete(self, line, text, cmd='show ', config=True, filter_blank_values=False): """ line - the full line of text (e.g. show fermentation text - the text fragment autom completing (e.g. fermentation) Note: cmd2 will swallow any exceptions and the command-line-completion won't behave as we expect. Examples: line text result 'show ' '' ['brewhouse ' ', 'ingredients ', 'recipes '] 'show br' 'br' ['brewhouse '] if text = '' then we search datastore for the full pth if text != '' then we have to search the datastore for everything except the prtial element. """ if config: database = 'config' else: database = 'oper' try: strip_partial_elements = 0 # Attempt to get the path which might not exist cmds = [] try: if not text == '': strip_partial_elements = 1 path_to_find = decode_path_string( line[len(cmd):], ignore_last_n=strip_partial_elements, ignore_root=True) slash_path = convert_path_to_slash_string(path_to_find) xcmds = self.pyconfhoarddata.list(path_to_find, separator='/') cmds = [] for key in xcmds: if key[0:len(text)] == text: cmds.append(key + ' ') except Exception as err: print(traceback.format_exc()) print(str(err)), '<auto-complete inner' pass cmds.sort() except Exception as err: print(traceback.format_exc()) print(str(err)) pass return cmds
def _command_conf_show(self, args): 'Show node in the configuration database' path = decode_path_string(args) try: print( self.pyconfhoarddata.get_database_as_json(path, database='config', pretty=True)) self._ok() except Exception as err: self._error(err)
def get(self, path_string, separator=' '): """ Get the value of a node from either the datastore, this returns whatever object type is requested. For accessing simple data get_keypath is quicker.. """ node_type = self.get_type(path_string, separator) path = decode_path_string(path_string, separator) try: return dpath.util.get(self.db, path) except KeyError: pass return None
def get_type(self, path_string, separator=' '): """ Return the type of a particular leaf from the model. """ self.log.trace('%s TYPE (separator %s)', path_string, separator) regex = re.compile("{([A-Za-z0-9]*)}\/?") path_string = regex.sub('/__listelement/', path_string) path = decode_path_string(path_string, separator) schema = dpath.util.get(self.schema, path) if '__listelement' in schema: return schema['__listelement']['__schema'] elif '__schema' in schema: return schema['__schema'] else: raise PyConfHoardAccessNonLeaf(path)
def _merge_keyval(self, key, val): self.log.trace('%s <- %s', key, val) # Get the schema. regex = re.compile("{([A-Za-z0-9]*)}\/?") updated_key = regex.sub('/__listelement/', key) if not updated_key[0:5] == '/root': updated_key = '/root' + updated_key if updated_key not in self.schema_by_path: try: schema = dpath.util.get(self.schema, updated_key) self.schema_by_path[updated_key] = self.schema except KeyError: raise PyConfHoardDataNoLongerRequired(updated_key) else: schema = self.schema_by_path[updated_key] val = self.validate_against_schema(schema, val, key) dpath.util.new(self.db, updated_key, val) self.keyval[key] = val # TODO: if we pass the schema we have to add into self.db # First cover off the simple case without lists, although # what we have done so far won't have made it harder. index = 0 if '__listelement' in updated_key: keys_in_path = regex.findall(key) path_to_work_on = updated_key for key_in_path in keys_in_path: # strip everything to the right of this key found_index = path_to_work_on.find('__listelement') left_part_of_key = path_to_work_on[0:found_index] right_part_of_key = path_to_work_on[found_index + len('__listelement'):] path = decode_path_string(left_part_of_key, separator='/') path.append(key_in_path) self._create_new_list_element_in_schema(path, key_in_path) path_to_work_on = left_part_of_key + key_in_path + '/' + right_part_of_key
def _lookup_datastore(self, path_string, database='config', separator='/'): path = decode_path_string(path_string, separator=separator, return_as_slash=True) if path == "/root": if database == 'config': return self.config else: return self.oper if path in self.map: return self.map[path][database] self.log.trace('MAP want %s: have: %s' % (path, self.map)) for data in self.map: path_trimmed = path[0:len(data)] if path_trimmed == data: return self.map[data][database] raise PyConfHoardDataPathNotRegistered(path_string)
def get_fragment(self, path_string, separator=' '): db = self.db path = decode_path_string(path_string, separator) fragment = dpath.util.get(db, path) return json.dumps(fragment, indent=4, sort_keys=True)
def _command_create(self, args): path_to_list = decode_path_string(args, ignore_last_n=1) key = decode_path_string(args, get_index=-1) self.config.create(path_to_list, key)