Module thunderlab.configfile
configfile
class ConfigFile: handling of configuration parameter.
Functions
def main()-
Expand source code
def main(): cfg = ConfigFile() cfg.add_section('Power spectrum:') cfg.add('nfft', 256, '', 'Number of data poinst for fourier transform.') cfg.add('windows', 4, '', 'Number of windows on which power spectra are computed.') cfg.add_section('Peaks:') cfg.add('threshold', 20.0, 'dB', 'Threshold for peak detection.') cfg.add('deltaf', 10.0, 'Hz', 'Minimum distance between peaks.') cfg.add('flip', True, '', 'Flip peaks.') cfg.write() print() print('set values:') s = cfg.set_values(['nfft: 1024, windows: 2', 'threshold: donkey', 'flip: false', 'something: blue']) if s is not None: print('errors:') for es in s: print(' ', es) cfg.write() print() print() print('delete nfft and windows:') del cfg['nfft'] del cfg['windows'] cfg.write()
Classes
class ConfigFile (orig=None)-
Expand source code
class ConfigFile(dict): """Handling of configuration parameter. Configuration parameter have a name (key), a value, a unit, a description and a default value. ConfigFile is a dictionary with the parameter names as keys and the tuple (value, unit, description, default) as value. New parameter can be added with the add() function. Configuration parameter can be further structured in the configuration file by inserting section titles via add_section(). The tuple (value, unit, description, default) can be retrieved via the name of a parameter using the [] operator. The value of a configuration parameter is retrieved by value() and set by set(). Values of several configuration parameter can be mapped to new names with the map() function. The resulting dictionary can be passed as key-word arguments to a function. The configuration parameter can be written to a configuration file with dump() and loaded from a file with load() and load_files(). Methods ------- - `add()`: add a new parameter to the configuration. - `add_section()`: add a new section to the configuration. - `value()`: returns the value of the configuration parameter defined by key. - `set(): set the value of the configuration parameter defined by key. - `set_values(): set values of configuration parameters from list of str. - `map()`: map the values of the configuration onto new names. - `write()`: pretty print configuration into file or stream. - `load()`: set values of configuration to values from key-value pairs read in from file. - `load_files()`: load configuration from current working directory as well as from several levels of a file path. """ def __init__(self, orig=None): super().__init__(dict()) self.sections = dict() self.new_section = None if not orig is None: for k, v in orig.items(): self[k] = list(v) for k, v in orig.sections.items(): self.sections[k] = v self.new_section = None def add(self, key, value, unit, description): """Add a new parameter to the configuration. The description of the parameter is a single string. Newline characters are intepreted as new paragraphs. Parameters ---------- key: str Key (name) of the parameter. value: any type Value of the parameter. unit: str Unit of the parameter value. description: str Textual description of the parameter. """ # add a pending section: if self.new_section is not None: self.sections[key] = self.new_section self.new_section = None # add configuration parameter (4th element is default value): self[key] = [value, unit, description, value] def add_section(self, description): """Add a new section to the configuration. Parameters ---------- description: str Textual description of the section """ self.new_section = description def value(self, key): """Returns the value of the configuration parameter defined by key. Parameters ---------- key: str Key of the configuration parameter. Returns ------- value: any type Value of the configuraion parameter. """ return self[key][0] def set(self, key, value): """Set the value of the configuration parameter defined by key. Parameters ---------- key: str Key of the configuration parameter. value: any type The new value. Raises ------ IndexError: If key does not exist. """ if not key in self: raise IndexError(f'Key {key} does not exist') self[key][0] = value def set_values(self, values): """Set values of configuration parameters from list of str. Parameters ---------- values: list of str Each string can be one or several key-value pairs separated by commas. A key-value pair is separated by colons. Returns ------- errors: list of str Error messages. """ errors = [] for kvs in values: for kv in kvs.strip().split(','): if ':' in kv: ss = kv.split(':') key = ss[0].strip() val = ':'.join(ss[1:]).strip() if key in self: vt = type(self[key][0]) if vt is bool: if val.lower() in ['true', 'on', 'yes', 't', 'y']: self[key][0] = True elif val.lower() in ['false', 'off', 'no', 'f', 'n']: self[key][0] = False else: errors.append(f'configuration parameter "{key}": cannot convert "{val}" to bool') else: try: self[key][0] = vt(val) except ValueError: errors.append(f'configuration parameter "{key}": cannot convert "{val}" to {vt}') else: errors.append(f'key "{key}" not found in configuration parameters') return errors if len(errors) > 0 else None def __delitem__(self, key): """Remove an entry from the configuration. Parameters ---------- key: str Key of the configuration parameter to be removed. """ if key in self.sections: sec = self.sections.pop(key) keys = list(self.keys()) inx = keys.index(key)+1 if inx < len(keys): next_key = keys[inx] if not next_key in self.sections: self.sections[next_key] = sec super().__delitem__(key) def map(self, mapping): """Map the values of the configuration onto new names. Use this function to generate key-word arguments that can be passed on to functions. Parameters ---------- mapping: dict Dictionary with its keys being the new names and its values being the parameter names of the configuration. Returns ------- a: dict A dictionary with the keys of mapping and the corresponding values retrieved from the configuration using the values from mapping. """ a = {} for dest, src in mapping.items(): if src in self: a[dest] = self.value(src) return a def write(self, fh=sys.stdout, header=None, diff_only=False, maxline=60, comments=True): """Pretty print configuration into file or stream. The description of a configuration parameter is printed out right before its key-value pair with an initial comment character ('#'). Section titles get two comment characters prependend ('##'). Lines are folded if the character count of parameter descriptions or section title exceeds maxline. A header can be printed initially. This is a simple string that is formatted like the section titles. Parameters ---------- fh: str or Path or file object Stream or file name for writing the configuration. header: str A string that is written as an introductory comment into the file. diff_only: bool If true write out only those parameters whose value differs from their default. maxline: int Maximum number of characters that fit into a line. comments: boolean Print out descriptions as comments if True. """ def write_comment(fh, comment, maxline, cs): # format comment: if len(comment) > 0: for line in comment.split('\n'): fh.write(cs + ' ') cc = len(cs) + 1 # character count for w in line.strip().split(' '): # line too long? if cc + len(w) > maxline: fh.write('\n' + cs + ' ') cc = len(cs) + 1 fh.write(w + ' ') cc += len(w) + 1 fh.write('\n') # open file: own_file = False if not hasattr(fh, 'write'): fh = open(fh, 'w') own_file = True # write header: First = True if comments and not header is None: write_comment(fh, header, maxline, '##') First = False # get length of longest key: maxkey = 0 for key in self.keys(): if maxkey < len(key): maxkey = len(key) # write out parameter: section = '' for key, v in self.items(): # possible section entry: if comments and key in self.sections: section = self.sections[key] # get value, unit, and comment from v: val = None unit = '' comment = '' differs = False if hasattr(v, '__len__') and (not isinstance(v, str)): val = v[0] if len(v) > 1 and len(v[1]) > 0: unit = ' ' + v[1] if len(v) > 2: comment = v[2] if len(v) > 3: differs = (val != v[3]) else: val = v # only write parameter whose value differs: if diff_only and not differs: continue # write out section if len(section) > 0: if not First: fh.write('\n\n') write_comment(fh, section, maxline, '##') section = '' First = False # write key-value pair: if comments : fh.write('\n') write_comment(fh, comment, maxline, '#') fh.write('{key:<{width}s}: {val}{unit:s}\n'.format( key=key, width=maxkey, val=val, unit=unit)) First = False # close file: if own_file: fh.close() def load(self, filename): """Set values of configuration to values from key-value pairs read in from file. Parameters ---------- filename: str or Path Name of the file from which to read the configuration. """ with open(filename, 'r') as f: for line in f: # do not process empty lines and comments: if len(line.strip()) == 0 or line[0] == '#' or not ':' in line: continue # parse key value pair: key, val = line.split(':', 1) key = key.strip() # only read values of existing keys: if not key in self: continue cv = self[key] vals = val.strip().split(' ') if hasattr(cv, '__len__') and (not isinstance(cv, str)): unit = '' if len(vals) > 1: unit = vals[1] if unit != cv[1]: print(f'unit for {key} is {unit} but should be {cv[1]}') if type(cv[0]) == bool: cv[0] = (vals[0].lower() == 'true' or vals[0].lower() == 'yes') else: try: cv[0] = type(cv[0])(vals[0]) except ValueError: cv[0] = vals[0] else: if type(cv[0]) == bool: self[key] = (vals[0].lower() == 'true' or vals[0].lower() == 'yes') else: try: self[key] = type(cv)(vals[0]) except ValueError: self[key] = vals[0] def load_files(self, cfgfile, filepath, maxlevel=3, verbose=0): """Load configuration from current working directory as well as from several levels of a file path. Parameters ---------- cfgfile: str or Path Name of the configuration file (without any path). filepath: str or Path Path of a file. Configuration files are read in from different levels of the expanded path. maxlevel: int Read configuration files from up to maxlevel parent directories. verbose: int If greater than zero, print out from which files configuration has been loaded. """ # load configuration from the current directory: cfgfile = Path(cfgfile) if cfgfile.is_file(): if verbose > 0: print(f'load configuration {cfgfile}') self.load(cfgfile) # load configuration files from higher directories: filepath = Path(filepath) parents = filepath.resolve().parents for k in range(min(maxlevel, len(parents))): path = parents[k] / cfgfile if path.is_file(): if verbose > 0: print(f'load configuration {path}') self.load(path)Handling of configuration parameter.
Configuration parameter have a name (key), a value, a unit, a description and a default value. ConfigFile is a dictionary with the parameter names as keys and the tuple (value, unit, description, default) as value.
New parameter can be added with the add() function.
Configuration parameter can be further structured in the configuration file by inserting section titles via add_section().
The tuple (value, unit, description, default) can be retrieved via the name of a parameter using the [] operator.
The value of a configuration parameter is retrieved by value() and set by set().
Values of several configuration parameter can be mapped to new names with the map() function. The resulting dictionary can be passed as key-word arguments to a function.
The configuration parameter can be written to a configuration file with dump() and loaded from a file with load() and load_files().
Methods
add(): add a new parameter to the configuration.add_section(): add a new section to the configuration.value(): returns the value of the configuration parameter defined by key.- `set(): set the value of the configuration parameter defined by key.
- `set_values(): set values of configuration parameters from list of str.
map(): map the values of the configuration onto new names.write(): pretty print configuration into file or stream.load(): set values of configuration to values from key-value pairs read in from file.load_files(): load configuration from current working directory as well as from several levels of a file path.
Ancestors
- builtins.dict
Methods
def add(self, key, value, unit, description)-
Expand source code
def add(self, key, value, unit, description): """Add a new parameter to the configuration. The description of the parameter is a single string. Newline characters are intepreted as new paragraphs. Parameters ---------- key: str Key (name) of the parameter. value: any type Value of the parameter. unit: str Unit of the parameter value. description: str Textual description of the parameter. """ # add a pending section: if self.new_section is not None: self.sections[key] = self.new_section self.new_section = None # add configuration parameter (4th element is default value): self[key] = [value, unit, description, value]Add a new parameter to the configuration.
The description of the parameter is a single string. Newline characters are intepreted as new paragraphs.
Parameters
key:str- Key (name) of the parameter.
value:any type- Value of the parameter.
unit:str- Unit of the parameter value.
description:str- Textual description of the parameter.
def add_section(self, description)-
Expand source code
def add_section(self, description): """Add a new section to the configuration. Parameters ---------- description: str Textual description of the section """ self.new_section = descriptionAdd a new section to the configuration.
Parameters
description:str- Textual description of the section
def value(self, key)-
Expand source code
def value(self, key): """Returns the value of the configuration parameter defined by key. Parameters ---------- key: str Key of the configuration parameter. Returns ------- value: any type Value of the configuraion parameter. """ return self[key][0]Returns the value of the configuration parameter defined by key.
Parameters
key:str- Key of the configuration parameter.
Returns
value:any type- Value of the configuraion parameter.
def set(self, key, value)-
Expand source code
def set(self, key, value): """Set the value of the configuration parameter defined by key. Parameters ---------- key: str Key of the configuration parameter. value: any type The new value. Raises ------ IndexError: If key does not exist. """ if not key in self: raise IndexError(f'Key {key} does not exist') self[key][0] = valueSet the value of the configuration parameter defined by key.
Parameters
key:str- Key of the configuration parameter.
value:any type- The new value.
Raises
Indexerror
If key does not exist.
def set_values(self, values)-
Expand source code
def set_values(self, values): """Set values of configuration parameters from list of str. Parameters ---------- values: list of str Each string can be one or several key-value pairs separated by commas. A key-value pair is separated by colons. Returns ------- errors: list of str Error messages. """ errors = [] for kvs in values: for kv in kvs.strip().split(','): if ':' in kv: ss = kv.split(':') key = ss[0].strip() val = ':'.join(ss[1:]).strip() if key in self: vt = type(self[key][0]) if vt is bool: if val.lower() in ['true', 'on', 'yes', 't', 'y']: self[key][0] = True elif val.lower() in ['false', 'off', 'no', 'f', 'n']: self[key][0] = False else: errors.append(f'configuration parameter "{key}": cannot convert "{val}" to bool') else: try: self[key][0] = vt(val) except ValueError: errors.append(f'configuration parameter "{key}": cannot convert "{val}" to {vt}') else: errors.append(f'key "{key}" not found in configuration parameters') return errors if len(errors) > 0 else NoneSet values of configuration parameters from list of str.
Parameters
values:listofstr- Each string can be one or several key-value pairs separated by commas. A key-value pair is separated by colons.
Returns
errors:listofstr- Error messages.
def map(self, mapping)-
Expand source code
def map(self, mapping): """Map the values of the configuration onto new names. Use this function to generate key-word arguments that can be passed on to functions. Parameters ---------- mapping: dict Dictionary with its keys being the new names and its values being the parameter names of the configuration. Returns ------- a: dict A dictionary with the keys of mapping and the corresponding values retrieved from the configuration using the values from mapping. """ a = {} for dest, src in mapping.items(): if src in self: a[dest] = self.value(src) return aMap the values of the configuration onto new names.
Use this function to generate key-word arguments that can be passed on to functions.
Parameters
mapping:dict- Dictionary with its keys being the new names and its values being the parameter names of the configuration.
Returns
a:dict- A dictionary with the keys of mapping and the corresponding values retrieved from the configuration using the values from mapping.
def write(self, fh=sys.stdout, header=None, diff_only=False, maxline=60, comments=True)-
Expand source code
def write(self, fh=sys.stdout, header=None, diff_only=False, maxline=60, comments=True): """Pretty print configuration into file or stream. The description of a configuration parameter is printed out right before its key-value pair with an initial comment character ('#'). Section titles get two comment characters prependend ('##'). Lines are folded if the character count of parameter descriptions or section title exceeds maxline. A header can be printed initially. This is a simple string that is formatted like the section titles. Parameters ---------- fh: str or Path or file object Stream or file name for writing the configuration. header: str A string that is written as an introductory comment into the file. diff_only: bool If true write out only those parameters whose value differs from their default. maxline: int Maximum number of characters that fit into a line. comments: boolean Print out descriptions as comments if True. """ def write_comment(fh, comment, maxline, cs): # format comment: if len(comment) > 0: for line in comment.split('\n'): fh.write(cs + ' ') cc = len(cs) + 1 # character count for w in line.strip().split(' '): # line too long? if cc + len(w) > maxline: fh.write('\n' + cs + ' ') cc = len(cs) + 1 fh.write(w + ' ') cc += len(w) + 1 fh.write('\n') # open file: own_file = False if not hasattr(fh, 'write'): fh = open(fh, 'w') own_file = True # write header: First = True if comments and not header is None: write_comment(fh, header, maxline, '##') First = False # get length of longest key: maxkey = 0 for key in self.keys(): if maxkey < len(key): maxkey = len(key) # write out parameter: section = '' for key, v in self.items(): # possible section entry: if comments and key in self.sections: section = self.sections[key] # get value, unit, and comment from v: val = None unit = '' comment = '' differs = False if hasattr(v, '__len__') and (not isinstance(v, str)): val = v[0] if len(v) > 1 and len(v[1]) > 0: unit = ' ' + v[1] if len(v) > 2: comment = v[2] if len(v) > 3: differs = (val != v[3]) else: val = v # only write parameter whose value differs: if diff_only and not differs: continue # write out section if len(section) > 0: if not First: fh.write('\n\n') write_comment(fh, section, maxline, '##') section = '' First = False # write key-value pair: if comments : fh.write('\n') write_comment(fh, comment, maxline, '#') fh.write('{key:<{width}s}: {val}{unit:s}\n'.format( key=key, width=maxkey, val=val, unit=unit)) First = False # close file: if own_file: fh.close()Pretty print configuration into file or stream.
The description of a configuration parameter is printed out right before its key-value pair with an initial comment character ('#').
Section titles get two comment characters prependend ('##').
Lines are folded if the character count of parameter descriptions or section title exceeds maxline.
A header can be printed initially. This is a simple string that is formatted like the section titles.
Parameters
fh:strorPathorfile object- Stream or file name for writing the configuration.
header:str- A string that is written as an introductory comment into the file.
diff_only:bool- If true write out only those parameters whose value differs from their default.
maxline:int- Maximum number of characters that fit into a line.
comments:boolean- Print out descriptions as comments if True.
def load(self, filename)-
Expand source code
def load(self, filename): """Set values of configuration to values from key-value pairs read in from file. Parameters ---------- filename: str or Path Name of the file from which to read the configuration. """ with open(filename, 'r') as f: for line in f: # do not process empty lines and comments: if len(line.strip()) == 0 or line[0] == '#' or not ':' in line: continue # parse key value pair: key, val = line.split(':', 1) key = key.strip() # only read values of existing keys: if not key in self: continue cv = self[key] vals = val.strip().split(' ') if hasattr(cv, '__len__') and (not isinstance(cv, str)): unit = '' if len(vals) > 1: unit = vals[1] if unit != cv[1]: print(f'unit for {key} is {unit} but should be {cv[1]}') if type(cv[0]) == bool: cv[0] = (vals[0].lower() == 'true' or vals[0].lower() == 'yes') else: try: cv[0] = type(cv[0])(vals[0]) except ValueError: cv[0] = vals[0] else: if type(cv[0]) == bool: self[key] = (vals[0].lower() == 'true' or vals[0].lower() == 'yes') else: try: self[key] = type(cv)(vals[0]) except ValueError: self[key] = vals[0]Set values of configuration to values from key-value pairs read in from file.
Parameters
filename:strorPath- Name of the file from which to read the configuration.
def load_files(self, cfgfile, filepath, maxlevel=3, verbose=0)-
Expand source code
def load_files(self, cfgfile, filepath, maxlevel=3, verbose=0): """Load configuration from current working directory as well as from several levels of a file path. Parameters ---------- cfgfile: str or Path Name of the configuration file (without any path). filepath: str or Path Path of a file. Configuration files are read in from different levels of the expanded path. maxlevel: int Read configuration files from up to maxlevel parent directories. verbose: int If greater than zero, print out from which files configuration has been loaded. """ # load configuration from the current directory: cfgfile = Path(cfgfile) if cfgfile.is_file(): if verbose > 0: print(f'load configuration {cfgfile}') self.load(cfgfile) # load configuration files from higher directories: filepath = Path(filepath) parents = filepath.resolve().parents for k in range(min(maxlevel, len(parents))): path = parents[k] / cfgfile if path.is_file(): if verbose > 0: print(f'load configuration {path}') self.load(path)Load configuration from current working directory as well as from several levels of a file path.
Parameters
cfgfile:strorPath- Name of the configuration file (without any path).
filepath:strorPath- Path of a file. Configuration files are read in from different levels of the expanded path.
maxlevel:int- Read configuration files from up to maxlevel parent directories.
verbose:int- If greater than zero, print out from which files configuration has been loaded.