No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

settings_loader.py 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #
  2. # This file is part of Lodel 2 (https://github.com/OpenEdition)
  3. #
  4. # Copyright (C) 2015-2017 Cléo UMS-3287
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as published
  8. # by the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import configparser
  20. import os
  21. import glob
  22. import copy
  23. from lodel.context import LodelContext
  24. #  @package lodel.settings.settings_loader Lodel2 loader of configuration options
  25. #
  26. # From a filesystem directory, all ini files are loaded in a dict (key/value) for each option
  27. # The options are called one by one by lodel bootstrap, if one or more options remains
  28. # then an exception is raised
  29. LodelContext.expose_modules(globals(), {
  30. 'lodel.logger': 'logger',
  31. 'lodel.settings.utils': ['SettingsError', 'SettingsErrors'],
  32. 'lodel.validator.validator': ['ValidationError']})
  33. ##@brief Merges and loads configuration files
  34. class SettingsLoader(object):
  35. ## To avoid the DEFAULT section whose values are found in all sections, we
  36. # have to give it an unsual name
  37. DEFAULT_SECTION = 'lodel2_default_passaway_tip'
  38. ## @brief Virtual filename when default value is used
  39. DEFAULT_FILENAME = 'default_value'
  40. ##@brief Constructor
  41. # @param conf_path str : conf.d path
  42. def __init__(self, conf_path):
  43. self.__conf_path = conf_path
  44. self.__conf_sv = dict()
  45. self.__conf = self.__merge()
  46. # Stores errors
  47. self.__errors_list = []
  48. ##@brief Lists and merges files in settings_loader.conf_path
  49. # @return dict()
  50. def __merge(self):
  51. conf = dict()
  52. l_dir = glob.glob(self.__conf_path+'/*.ini')
  53. logger.debug("SettingsLoader found those settings files : %s" % (
  54. ', '.join(l_dir)))
  55. for f_ini in l_dir:
  56. config = configparser.ConfigParser(
  57. default_section = self.DEFAULT_SECTION ,interpolation=None)
  58. config.read(f_ini)
  59. for section in [s for s in config if s != self.DEFAULT_SECTION]:
  60. if section not in conf:
  61. conf[section] = dict()
  62. for param in config[section]:
  63. if param not in conf[section]:
  64. conf[section][param] = dict()
  65. conf[section][param]['value'] = config[section][param]
  66. conf[section][param]['file'] = f_ini
  67. self.__conf_sv[section + ':' + param] = f_ini
  68. else:
  69. raise SettingsError("Error redeclaration of key %s \
  70. in section %s. Found in %s and %s" % (\
  71. section, param, f_ini, conf[section][param]['file']))
  72. return conf
  73. ##@brief Returns option if exists default_value else and validates
  74. # @param section str : name of the section
  75. # @param keyname str
  76. # @param validator callable : takes one argument value and raises validation fail
  77. # @param default_value *
  78. # @return the option
  79. def getoption(self,section,keyname,validator,default_value=None):
  80. conf=self.__conf
  81. if section not in conf:
  82. conf[section] = dict()
  83. sec = conf[section]
  84. if keyname in sec:
  85. result = sec[keyname]['value']
  86. try:
  87. del self.__conf_sv[section + ':' + keyname]
  88. except KeyError: #allready fetched
  89. pass
  90. else:
  91. #default values
  92. sec[keyname] = dict()
  93. result = sec[keyname]['value'] = default_value
  94. sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME
  95. try:
  96. return validator(result)
  97. except Exception as e:
  98. # Generating nice exceptions
  99. if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME:
  100. expt = SettingsError(msg='Mandatory settings not found', \
  101. key_id=section+'.'+keyname)
  102. self.__errors_list.append(expt)
  103. else:
  104. expt = ValidationError("For %s.%s : %s" % (section, keyname, e))
  105. expt2 = SettingsError(msg=str(expt), \
  106. key_id=section+'.'+keyname, \
  107. filename=sec[keyname]['file'])
  108. self.__errors_list.append(expt2)
  109. return
  110. ##@brief Sets option in a config section. Writes in the conf file
  111. # @param section str : name of the section
  112. # @param keyname str
  113. # @param value str
  114. # @param validator callable : takes one argument value and raises validation fail
  115. # @return the option
  116. def setoption(self, section, keyname, value, validator):
  117. f_conf = copy.copy(self.__conf[section][keyname]['file'])
  118. if f_conf == SettingsLoader.DEFAULT_FILENAME:
  119. f_conf = self.__conf_path + '/generated.ini'
  120. conf = self.__conf
  121. conf[section][keyname] = value
  122. config = configparser.ConfigParser()
  123. config.read(f_conf)
  124. if section not in config:
  125. config[section] = {}
  126. config[section][keyname] = validator(value)
  127. with open(f_conf, 'w') as configfile:
  128. config.write(configfile)
  129. ##@brief Saves new partial configuration. Writes in the conf files corresponding
  130. # @param sections dict
  131. # @param validators dict of callable : takes one argument value and raises validation fail
  132. def saveconf(self, sections, validators):
  133. for sec in sections:
  134. for kname in sections[sec]:
  135. self.setoption(sec, kname, sections[sec][kname], validators[sec][kname])
  136. ##@brief Returns the section to be configured
  137. # @param section_prefix str
  138. # @param default_section str
  139. # @return the section as dict()
  140. def getsection(self, section_prefix, default_section=None):
  141. conf = copy.copy(self.__conf)
  142. sections = []
  143. if section_prefix in conf:
  144. sections.append(section_prefix)
  145. for sect_names in conf:
  146. if sect_names in sections:
  147. pass
  148. elif sect_names.startswith(section_prefix + '.'):
  149. sections.append(sect_names)
  150. if sections == [] and default_section:
  151. sections.append(section_prefix + '.' + default_section)
  152. elif sections == []:
  153. raise NameError("Not existing settings section : %s" % section_prefix)
  154. return sections
  155. ##@brief Return invalid settings
  156. #
  157. # This method returns all the settings that was not fetched by
  158. # getsection() method. For the Settings object it allows to know
  159. # the list of invalids settings keys
  160. # @return a dict with SECTION_NAME+":"+KEY_NAME as key and the filename
  161. # where the settings was found as value
  162. def getremains(self):
  163. return self.__conf_sv
  164. ##@brief Raise a SettingsErrors exception if some confs remain
  165. #@note typically used at the end of Settings bootstrap
  166. def raise_errors(self):
  167. remains = self.getremains()
  168. err_l = self.__errors_list
  169. for key_id, filename in remains.items():
  170. err_l.append(SettingsError(msg="Invalid configuration key", \
  171. key_id=key_id, \
  172. filename =filename))
  173. if len(err_l) > 0:
  174. raise SettingsErrors(err_l)
  175. else:
  176. return