#-*- coding: utf-8 -*- import configparser import os import glob import copy from lodel.context import LodelContext LodelContext.expose_modules(globals(), { 'lodel.logger': 'logger', 'lodel.settings.utils': ['SettingsError', 'SettingsErrors'], 'lodel.validator.validator': ['ValidationError']}) ##@brief Merges and loads configuration files class SettingsLoader(object): ## To avoid the DEFAULT section whose values are found in all sections, we # have to give it an unsual name DEFAULT_SECTION = 'lodel2_default_passaway_tip' ## @brief Virtual filename when default value is used DEFAULT_FILENAME = 'default_value' ##@brief Constructor # @param conf_path str : conf.d path def __init__(self, conf_path): self.__conf_path = conf_path self.__conf_sv = dict() self.__conf = self.__merge() # Stores errors self.__errors_list = [] ##@brief Lists and merges files in settings_loader.conf_path # @return dict() def __merge(self): conf = dict() l_dir = glob.glob(self.__conf_path+'/*.ini') logger.debug("SettingsLoader found those settings files : %s" % ( ', '.join(l_dir))) for f_ini in l_dir: config = configparser.ConfigParser(default_section=self.DEFAULT_SECTION, interpolation=None) config.read(f_ini) for section in [s for s in config if s != self.DEFAULT_SECTION]: if section not in conf: conf[section] = dict() for param in config[section]: if param not in conf[section]: conf[section][param] = dict() conf[section][param]['value'] = config[section][param] conf[section][param]['file'] = f_ini self.__conf_sv[section + ':' + param] = f_ini else: raise SettingsError("Error redeclaration of key %s \ in section %s. Found in %s and %s" % (\ section, param, f_ini, conf[section][param]['file'])) return conf ##@brief Returns option if exists default_value else and validates # @param section str : name of the section # @param keyname str # @param validator callable : takes one argument value and raises validation fail # @param default_value * # @param mandatory bool # @return the option def getoption(self, section, keyname, validator, default_value=None, mandatory=False): conf = self.__conf if section not in conf: conf[section] = dict() sec = conf[section] result = None if keyname in sec: result = sec[keyname]['value'] if result is not None: result = result.strip() if len(result) == 0: result = None try: del self.__conf_sv[section + ':' + keyname] except KeyError: #allready fetched pass if result is None: if default_value is None and mandatory: msg = "Default value mandatory for option %s" % keyname expt = SettingsError(msg=msg, key_id=section+'.'+keyname, \ filename=sec[keyname]['file']) self.__errors_list.append(expt) return else: sec[keyname] = dict() sec[keyname]['value'] = default_value sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME result = default_value logger.debug("Using default value for configuration key %s:%s" \ % (section, keyname)) try: return validator(result) except Exception as e: # Generating nice exceptions if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME: expt = SettingsError(msg='Mandatory settings not found', \ key_id=section+'.'+keyname) self.__errors_list.append(expt) else: expt = ValidationError("For %s.%s : %s" % (section, keyname, e)) expt2 = SettingsError(msg=str(expt), \ key_id=section+'.'+keyname, \ filename=sec[keyname]['file']) self.__errors_list.append(expt2) return ##@brief Sets option in a config section. Writes in the conf file # @param section str : name of the section # @param keyname str # @param value str # @param validator callable : takes one argument value and raises validation fail # @return the option def setoption(self, section, keyname, value, validator): f_conf = copy.copy(self.__conf[section][keyname]['file']) if f_conf == SettingsLoader.DEFAULT_FILENAME: f_conf = self.__conf_path + '/generated.ini' conf = self.__conf conf[section][keyname] = value config = configparser.ConfigParser() config.read(f_conf) if section not in config: config[section] = {} config[section][keyname] = validator(value) with open(f_conf, 'w') as configfile: config.write(configfile) ##@brief Saves new partial configuration. Writes in the conf files corresponding # @param sections dict # @param validators dict of callable : takes one argument value and raises validation fail def saveconf(self, sections, validators): for sec in sections: for kname in sections[sec]: self.setoption(sec, kname, sections[sec][kname], validators[sec][kname]) ##@brief Returns the section to be configured # @param section_prefix str # @param default_section str # @return the section as dict() def getsection(self, section_prefix, default_section=None): conf = copy.copy(self.__conf) sections = [] if section_prefix in conf: sections.append(section_prefix) for sect_names in conf: if sect_names in sections: pass elif sect_names.startswith(section_prefix + '.'): sections.append(sect_names) if sections == [] and default_section: sections.append(section_prefix + '.' + default_section) elif sections == []: raise NameError("Not existing settings section : %s" % section_prefix) return sections ##@brief Returns invalid settings # # This method returns all the settings that was not fecthed by # getsection() method. For the Settings object it allows to know # the list of invalids settings keys # @return a dict with SECTION_NAME+":"+KEY_NAME as key and the filename # where the settings was found as value def getremains(self): return self.__conf_sv ##@brief Raise a SettingsErrors exception if some confs remains #@note typically used at the end of Settings bootstrap def raise_errors(self): remains = self.getremains() err_l = self.__errors_list for key_id, filename in remains.items(): err_l.append(SettingsError(msg="Invalid configuration key", \ key_id=key_id, \ filename =filename)) if len(err_l) > 0: raise SettingsErrors(err_l) else: return