Source code for in3Utils.cfgUtils

'''
    Utilities for handling configuration files

'''

import configparser
import logging
import pathlib
import hashlib
import json
import pandas as pd
import numpy as np

# Local imports
import avaframe as avaf
from avaframe.in3Utils import logUtils
from avaframe.in3Utils import fileHandlerUtils as fU


log = logging.getLogger(__name__)


[docs]def getGeneralConfig(): ''' Returns the general configuration for avaframe returns a configParser object ''' # get path of module modPath = pathlib.Path(avaf.__file__).resolve().parent localFile = modPath / 'local_avaframeCfg.ini' defaultFile = modPath / 'avaframeCfg.ini' if localFile.is_file(): iniFile = localFile iniFile = [defaultFile, localFile] compare = True elif defaultFile.is_file(): iniFile = defaultFile compare = False else: raise FileNotFoundError('None of the provided cfg files exist ') # Finally read it cfg, _ = compareConfig(iniFile, 'General', compare) return cfg
[docs]def getModuleConfig(module, fileOverride='', modInfo=False, toPrint=True, onlyDefault=False): ''' Returns the configuration for a given module returns a configParser object module object: module : the calling function provides the already imported module eg.: from avaframe.com2AB import com2AB leads to getModuleConfig(com2AB) whereas from avaframe.com2AB import com2AB as c2 leads to getModuleConfig(c2) Str: fileOverride : allows for a completely different file location modInfo: bool true if dictionary with info on differences to standard config onlyDefault: bool if True, only use the default configuration Order is as follows: fileOverride -> local_MODULECfg.ini -> MODULECfg.ini ''' # get path of module modPath = pathlib.Path(module.__file__).resolve().parent # get filename of module modName = str(pathlib.Path(module.__file__).stem) localFile = modPath / ('local_'+modName+'Cfg.ini') defaultFile = modPath / (modName+'Cfg.ini') log.debug('localFile: %s', localFile) log.debug('defaultFile: %s', defaultFile) # Decide which one to take if fileOverride: fileOverride = fU.checkPathlib(fileOverride) if fileOverride.is_file(): iniFile = [defaultFile, fileOverride] compare = True else: raise FileNotFoundError('Provided fileOverride does not exist: ' + str(fileOverride)) elif localFile.is_file() and not onlyDefault: iniFile = localFile iniFile = [defaultFile, localFile] compare = True elif defaultFile.is_file(): iniFile = defaultFile compare = False else: raise FileNotFoundError('None of the provided cfg files exist ') # Finally read it cfg, modDict = compareConfig(iniFile, modName, compare, modInfo, toPrint) if modInfo: return cfg, modDict return cfg
[docs]def getDefaultModuleConfig(module, toPrint=True): ''' Returns the default configuration for a given module returns a configParser object module object: module : the calling function provides the already imported module eg.: from avaframe.com2AB import com2AB leads to getModuleConfig(com2AB) whereas from avaframe.com2AB import com2AB as c2 leads to getModuleConfig(c2) ''' # get path of module modPath = pathlib.Path(module.__file__).resolve().parent # get filename of module modName = str(pathlib.Path(module.__file__).stem) defaultFile = modPath / (modName+'Cfg.ini') log.debug('defaultFile: %s', defaultFile) # Finally read it cfg, _ = compareConfig(defaultFile, modName, compare=False, toPrint=toPrint) return cfg
[docs]def compareConfig(iniFile, modName, compare, modInfo=False, toPrint=True): ''' Compare configuration files (if a local and default are both provided) and inform user of the eventuel differences. Take the default as reference. Inputs: -iniFile: path to config file. Only one path if compare=False -compare: True if two paths are provided and a comparison is needed -modInfo: True if dictionary with modifications shall be returned -toPrint: True print configuration to terminal Output: ConfigParser object ''' modDict = {} if compare: log.info('Reading config from: %s and %s' % (iniFile[0], iniFile[1])) # initialize our final configparser object cfg = configparser.ConfigParser() cfg.optionxform = str # initialize configparser object to read defCfg = configparser.ConfigParser() defCfg.optionxform = str locCfg = configparser.ConfigParser() locCfg.optionxform = str # read default and local parser files defCfg.read(iniFile[0]) locCfg.read(iniFile[1]) # loop through all sections of the defCfg log.debug('Writing cfg for: %s', modName) for section in defCfg.sections(): modDict[section] = {} cfg.add_section(section) log.info('\t%s', section) for key in defCfg.items(section): defValue = key[1] # check if key is also in the localCfg if locCfg.has_option(section, key[0]): locValue = locCfg.get(section, key[0]) if locValue != defValue: # if yes and if this value is different add this key to # the cfg that will be returned locValue = locCfg.get(section, key[0]) cfg.set(section, key[0], locValue) log.info('\t\t%s : %s \t(default value was : %s)', key[0], locValue, defValue) modString = [locValue, defValue] modDict[section][key[0]] = modString else: cfg.set(section, key[0], defValue) log.info('\t\t%s : %s', key[0], defValue) # remove the key from the localCfg locCfg.remove_option(section, key[0]) else: cfg.set(section, key[0], defValue) log.info('\t\t%s : %s', key[0], defValue) # Now check if there are some sections/ keys left in the local cfg and # that are not used for section in locCfg.sections(): if defCfg.has_section(section): for key in locCfg.items(section): # an exception is made for thickness values that are added for the features of a releaseScenario, # entrainment Scenario or secondar. release scenario # these are added to the configuration and also to the modDict if variation is applied validItems = ['entrainmentScenario', 'DEM', 'secondaryReleaseScenario'] searchItems = ['relTh', 'entTh', 'secondaryRelTh'] if any(s in key[0] for s in searchItems) or key[0] in validItems: locValue = locCfg.get(section, key[0]) cfg.set(section, key[0], locValue) log.debug('\t\t%s : %s added to %s' % (key[0], locValue, section)) if '$' in locValue: modString = [locValue, locValue.split('$')[0]] modDict[section][key[0]] = modString else: log.warning('Additional Key [\'%s\'] in section [\'%s\'] is ignored.' % (key[0], section)) else: cfg.add_section(section) log.info('Additional section [\'%s\'] is added to the configuration.' % (section)) for key in locCfg.items(section): log.info('Additional Key [\'%s\'] in section [\'%s\'] is added to the configuration.' % (key[0], section)) cfg.set(section, key[0], key[1]) log.info('\t\t%s : %s', key[0], key[1]) else: log.info('Reading config from: %s', iniFile) cfg = configparser.ConfigParser() cfg.optionxform = str # Finally read it cfg.read(iniFile) # Write config to log file if toPrint: logUtils.writeCfg2Log(cfg, modName) return cfg, modDict
[docs]def writeCfgFile(avaDir, module, cfg, fileName=''): """ Save configuration used to text file in Outputs as moduleName_settings.ini or optional in Outputs/moduleName/configurationFiles/filenName.ini Parameters ----------- avaDir: str path to avalanche directory module: module cfg: configparser object configuration settings fileName: str name of saved configuration file - optional """ # get filename of module name = pathlib.Path(module.__file__).name modName = name.split('.')[0] # write to file if fileName != '': # set outputs outDir = pathlib.Path(avaDir, 'Outputs', modName, 'configurationFiles') fU.makeADir(outDir) cfg.optionxform = str with open(pathlib.Path(outDir, '%s.ini' % (fileName)), 'w') as conf: cfg.write(conf) else: # set outputs outDir = pathlib.Path(avaDir, 'Outputs') cfg.optionxform = str with open(pathlib.Path(outDir, '%s_settings.ini' % (modName)), 'w') as conf: cfg.write(conf)
[docs]def readCfgFile(avaDir, module='', fileName=''): """ Read configuration from ini file, if module is provided, module configuration is read from Ouputs, if fileName is provided configuration is read from fileName Parameters ----------- avaDir: str path to avalanche directory module: module fileName: str path to file that should be read - optional Returns -------- cfg: configParser object configuration that is from file """ # define file that should be read if fileName != '': inFile = fileName elif module != '': # get module name name = pathlib.Path(module.__file__).name modName = name.split('.')[0] # set input file inFile = pathlib.Path(avaDir, 'Outputs', '%s_settings.ini' % (modName)) else: log.error('Please provide either a module or a fileName to read configuration from file') raise NameError # read configParser object from input file, case sensitive cfg = configparser.ConfigParser() cfg.optionxform = str cfg.read(inFile) cfg.optionxform = str return cfg
[docs]def cfgHash(cfg, typeDict=False): """ UID hash of a config. Given a configParser object cfg, or a dictionary - then typeDict=True, returns a uid hash Parameters ---------- cfg: configParser object typeDict : dict dictionary Returns: -------- uid: str uid hash """ uidHash = hashlib.shake_256() if typeDict: cfgDict = cfg else: cfgDict = convertConfigParserToDict(cfg) jsonDict = json.dumps(cfgDict, sort_keys=True, ensure_ascii=True) encoded = jsonDict.encode() uidHash.update(encoded) uid = uidHash.hexdigest(5) return uid
[docs]def convertConfigParserToDict(cfg): """ create dictionary from configparser object """ cfgDict = {} for section in cfg.sections(): cfgDict[section] = {} for key, val in cfg.items(section): cfgDict[section][key] = val return cfgDict
[docs]def convertDictToConfigParser(cfgDict): """ create configParser object from dict """ cfg = configparser.ConfigParser() cfg.optionxform = str for section in cfgDict: cfg[section] = cfgDict[section] return cfg
[docs]def writeDictToJson(inDict, outFilePath): """ write a dictionary to a json file """ jsonDict = json.dumps(inDict, sort_keys=True, ensure_ascii=True) f = open(outFilePath, "w") f.write(jsonDict) f.close()
[docs]def createConfigurationInfo(avaDir, comModule='com1DFA', standardCfg='', writeCSV=False, specDir=''): """ Read configurations from all simulations configuration ini files from directory Parameters ----------- avaDir: str path to avalanche directory standardCfg: dict standard configuration for module - option writeCSV: bool True if configuration dataFrame shall be written to csv file specDir: str path to a directory where simulation configuration files can be found - optional Returns -------- simDF: pandas DataFrame DF with all the simulation configurations """ # collect all configuration files for this module from directory if specDir != '': inDir = pathlib.Path(specDir, 'configurationFiles') else: inDir = pathlib.Path(avaDir, 'Outputs', comModule, 'configurationFiles') configFiles = inDir.glob('*.ini') if not inDir.is_dir(): message = 'configuration file directory not found: %s' % (inDir) log.error(message) raise NotADirectoryError(message) elif configFiles == []: message = 'No configuration file found in: %s' % (inDir) log.error(message) raise FileNotFoundError(message) # create confiparser object, convert to json object, write to dataFrame # append all dataFrames simDF = '' for cFile in configFiles: if 'sourceConfiguration' not in str(cFile): simName = pathlib.Path(cFile).stem if '_AF_' in simName: nameParts = simName.split('_AF_') infoParts = nameParts[1].split('_') else: nameParts = simName.split('_') infoParts = nameParts[1:] simHash = infoParts[0] cfgObject = readCfgFile(avaDir, fileName=cFile) simDF = appendCgf2DF(simHash, simName, cfgObject, simDF) # convert numeric parameters to numerics simDF = convertDF2numerics(simDF) # add default configuration if standardCfg != '': # read default configuration of this module simDF = appendCgf2DF('current standard', 'current standard', standardCfg, simDF) # if writeCSV, write dataFrame to csv file if writeCSV: writeAllConfigurationInfo(avaDir, simDF, specDir=specDir) return simDF
[docs]def appendCgf2DF(simHash, simName, cfgObject, simDF): """ append simulation configuration to the simulation dataframe only account for sections GENERAL and INPUT Parameters ----------- simHash: str hash of the simulation to append simName: str name of the simulation cfgObject: configParser configuration coresponding to the simulation simDF: pandas dataFrame configuration dataframe Returns -------- simDF: pandas DataFrame DFappended with the new simulation configuration """ indexItem = [simHash] cfgDict = convertConfigParserToDict(cfgObject) simItemDFGeneral = pd.DataFrame(data=cfgDict['GENERAL'], index=indexItem) simItemDFInput = pd.DataFrame(data=cfgDict['INPUT'], index=indexItem) simItemDF = pd.concat([simItemDFGeneral, simItemDFInput], axis=1) simItemDF = simItemDF.assign(simName=simName) if isinstance(simDF, str): simDF = simItemDF else: simDF = pd.concat([simDF, simItemDF], axis=0) return simDF
[docs]def appendTcpu2DF(simHash, tCPU, tCPUDF): """ append Tcpu dictionary to the dataframe Parameters ----------- simHash: str hash of the simulation corresponding to the tCPU dict to append tCPU: dict cpu time dict of the simulation tCPUDF: pandas dataFrame tCPU dataframe Returns -------- simDF: pandas DataFrame DFappended with the new simulation configuration """ indexItem = [simHash] tCPUItemDF = pd.DataFrame(data=tCPU, index=indexItem) if isinstance(tCPUDF, str): tCPUDF = tCPUItemDF else: tCPUDF = pd.concat([tCPUDF, tCPUItemDF], axis=0) return tCPUDF
[docs]def convertDF2numerics(simDF): """ convert a string DF to a numerical one Parameters ----------- simDF: pandas dataFrame dataframe Returns -------- simDF: pandas DataFrame """ for name, values in simDF.iteritems(): simDFTest = simDF[name].str.replace('.', '', regex=True) # allow for - sign too simDFTest = simDFTest.replace('-', '', regex=True) # also include columns where nan is in first row - so check for any row if simDFTest.str.isdigit().any(): # problem here is that it finds even if not present in | although not in ini simDFTest = simDF[name].str.replace('|', '§', regex=True) if simDFTest.str.contains('§').any() == False: simDF[name] = pd.to_numeric(simDF[name]) log.debug('Converted to numeric %s' % name) else: log.debug('Not converted to numeric: %s' % name) return simDF
[docs]def readAllConfigurationInfo(avaDir, specDir=''): """ Read allConfigurations.csv file as dataFrame from directory Parameters ----------- avaDir: str path to avalanche directory specDir: str path to a directory where simulation configuration files can be found - optional Returns -------- simDF: pandas DataFrame DF with all the simulation configurations simDFName: array simName column of the dataframe """ # collect all configuration files for this module from directory if specDir != '': inDir = pathlib.Path(specDir, 'configurationFiles') else: inDir = pathlib.Path(avaDir, 'Outputs', 'com1DFA', 'configurationFiles') configFiles = inDir / 'allConfigurations.csv' if configFiles.is_file(): with open(configFiles, 'rb') as file: simDF = pd.read_csv(file, index_col=0, keep_default_na=False) simDFName = simDF['simName'].to_numpy() else: simDF = None simDFName = [] return simDF, simDFName
[docs]def writeAllConfigurationInfo(avaDir, simDF, specDir=''): """ Write cfg configuration to allConfigurations.csv Parameters ----------- avaDir: str path to avalanche directory simDF: pandas dataFrame daaframe of the configuration specDir: str path to a directory where simulation configuration shal be saved - optional Returns -------- configFiles: pathlib Path path where the configuration dataframe was saved """ # collect all configuration files for this module from directory if specDir != '': inDir = pathlib.Path(specDir, 'configurationFiles') else: inDir = pathlib.Path(avaDir, 'Outputs', 'com1DFA', 'configurationFiles') configFiles = inDir / 'allConfigurations.csv' simDF.to_csv(configFiles) return configFiles