# -*- coding: utf-8 -*- # # This file is part of configparser_extended library # released under the MIT license. # See the LICENSE file for more information. import configparser from configparser import NoOptionError, NoSectionError try: from backports.configparser.helpers import OrderedDict except ImportError: from collections import OrderedDict from six import u # Used in parser getters to indicate the default behaviour when a specific # option is not found it to raise an exception. Created to enable `None' as # a valid fallback value. _UNSET = object() class ExtendedConfigParser(configparser.ConfigParser): # Contains the options read in the DEFAULT section default_section = None config_name = '' config_separator = '_' section_separator = ':' list_separator = ';' inheritance = 'explicit' father = {} # Contains the defaults values def __init__(self, defaults=None, dict_type=OrderedDict, allow_no_value=False, config='', config_separator='_', section_separator=':', list_separator=';', inheritance='explicit', **kwargs ): configparser.ConfigParser.__init__(self, defaults, dict_type, allow_no_value, **kwargs ) # Prevents the get() method from looking into the DEFAULT section # before looking into potential parents self.default_section = None self.config_name = config self.config_separator = config_separator self.section_separator = section_separator self.list_separator = list_separator self.inheritance = inheritance self._proxies[self. default_section] = SectionProxyExtended(self, configparser. DEFAULTSECT) # Prevents the get() method from looking into defaults before # looking into potential parents self.father = self._defaults.copy() self._defaults = {} def get(self, section, option, raw=False, vars=None, fallback=_UNSET, sect_first=True, cfg_plus=False, isList=False): """ Returns the value corresponding to an option in a particular section. If vars is provided, it must be a dictionary. The value is looked up in vars before being looked up in section. This function processes the section name and config name to converte them into valid names and then looks for the option value depending on these names. You can choose if you want to prioritize the section over the config name (sect_first = True) or the config name over the section (sect_first = False) if you absolutely want the value corresponding to the config. ex : Looking for 'option1' in 'section1' with a 'toto_titi' config [section1:section2] option1[toto]=toto1 option1=val1 [section2] option1[toto_titi]=toto_titi2 (sect_first = True) : option1 = toto1 (sect_first = False) : option1 = toto_titi2 """ if(sect_first): return self.section_config_loop(section, option, raw, vars, fallback, cfg_plus, isList) else: return self.config_section_loop(section, option, raw, vars, fallback, cfg_plus, isList) def section_config_loop(self, section, option, raw=False, vars=None, fallback=_UNSET, cfg_plus=False, isList=False): """ Loops on the configs inside a section run ex : Look for option with and without config name in vars Look for option with and without config name in section1:section2 Look for option with and without config name in section2 Look for option with and without config name in DEFAULT Look for option with and without config name in defaults """ result = None # If fallback is set, return fallback instead of raising # NoSectionError: if fallback != _UNSET: try: sections = self.get_corresponding_sections(section) except NoSectionError: return fallback else: sections = self.get_corresponding_sections(section) if(not cfg_plus): configs = self.get_configs() else: configs = self.get_configs_plus() if(vars is not None): for c in configs: if (vars.get(option + '[' + c + ']') is not None): return vars.get(option + '[' + c + ']') if (vars.get(option) is not None): return vars.get(option) # Loop on the list of sections for s in sections: # Loop on the config names for c in configs: if(self.get_result(s, option, raw, c, isList=isList) is not None): return self.get_result(s, option, raw, c, isList=isList) # Look for the option without any specific config name if(self.get_result(s, option, raw, isList=isList) is not None): return self.get_result(s, option, raw, isList=isList) # Look for the option in the DEFAULT section, in defaults and, finally, # in fallback for c in configs: if(result is None): if(option in self.default_section): result = self.default_section.get(option + '[' + c + ']') break for c in configs: if(result is None): if(option in self.father): result = self.father.get(option + '[' + c + ']') break if(result is None): if(option in self.default_section): result = self.default_section.get(option) if(result is None): if(option in self.father): result = self.father.get(option) if(result is None): result = fallback if(result is _UNSET): # If nothing has been found, raise an exception raise NoOptionError(option, section) if(isList and result is not fallback): result = self.convert_value_list(result) return result def config_section_loop(self, section, option, raw=False, vars=None, fallback=_UNSET, cfg_plus=False, isList=False): """ Loops on the sections per config name run ex : Look for option[cfg1] in every section Look for option[cfg2] in every section Look for option in every section """ result = None sections = self.get_corresponding_sections(section) if(not cfg_plus): configs = self.get_configs() else: configs = self.get_configs_plus() if(self.config_name != '' and self.config_name is not None): # Loop on the config names for c in configs: # Search into vars if(vars is not None): if (vars.get(option + '[' + c + ']') is not None): return vars.get(option + '[' + c + ']') # Loop on the list of sections for s in sections: if(self.get_result(s, option, raw, c, isList=isList) is not None): return self.get_result(s, option, raw, c, isList=isList) # Look for the option in the DEFAULT section, in defaults if(result is None): if(option + '[' + c + ']' in self.default_section): result = self.default_section.get(option + '[' + c + ']') break if(result is None): if(option + '[' + c + ']' in self.father): result = self.father.get(option + '[' + c + ']') break if(result is None): # Look for the option without any specific config name if(vars is not None): if (vars.get(option) is not None): return vars.get(option) # Loop on the list of sections for s in sections: if(self.get_result(s, option, raw, isList=isList) is not None): return self.get_result(s, option, raw, isList=isList) # Look for the option in the DEFAULT section, in defaults and, # finally, in fallback if(result is None): if(option in self.default_section): result = self.default_section.get(option) if(result is None): if(option in self.father): result = self.father.get(option) if(result is None): result = fallback if(result is _UNSET): # If nothing has been found, raise an exception raise NoOptionError(option, section) if(isList and result is not fallback): result = self.convert_value_list(result) return result def get_result(self, s, option, raw=False, c='', isList=False): if(c == '' or c is None): try: result = self.find_option(s, option, raw) if(result is not None): if(isList): result = self.convert_value_list(result) return result except NoOptionError: pass else: try: result = self.find_option(s, option, raw, c) if(result is not None): if(isList): result = self.convert_value_list(result) return result except NoOptionError: pass def find_option(self, section, option, raw=False, config=''): """ Returns the result of a basic get() where section and config have already been processed/converted into valid names. It functions just as the original get() does while being able to return a result associated with a config name. """ try: # If no config name is precised, look for the option without # "[config]" if(config == ''): return super(ExtendedConfigParser, self).get(section, option, raw=raw) # Look for the option with the config name specified between # a pair of brackets (ex : key1[config1]=val1 ) # NoSectionError raised by get_no_def() else: return super(ExtendedConfigParser, self).get(section, option + '[' + config + ']', raw=raw) except NoOptionError: pass def get_corresponding_sections(self, section): """ Look for the actual name of the section if it inherits from other sections and stores the section name itself as well as its parents'. """ if(self.inheritance == 'im' or self.inheritance == 'impl' or self.inheritance == 'implicit'): return self._get_corresponding_sections_inheritance(section) else: return self._get_corresponding_sections(section) def _get_corresponding_sections(self, section): """ Look for the actual name of the section if it inherits from other sections and stores the section name itself as well as its parents'. Searches only through parents explicitly defined in the section name """ # Find the corresponding section if it inherits from another section sect = self.get_section_name(section) # Store all of the valid section names corresponding to the initial # section (the section itself and its parents if they actually exist) sections = [] section_name_edited = self.get_section_name(section) while(self.section_separator in section_name_edited): if section_name_edited in self._sections: sections.append(section_name_edited) section_name_edited = section_name_edited[section_name_edited.find( self.section_separator) + 1:] # Section names alone, without parents section_splitted = sect.split(self.section_separator) for s in section_splitted: # If the section name exists if s in self._sections: sections.append(s) return sections def _get_corresponding_sections_inheritance(self, section): """ Look for the actual name of the section if it inherits from other sections and stores the section name itself as well as its parents'. This version is closer to the Object notion of inheritance since it also finds "abstract" parents ex : [sect1:sect2] and [sect2:sect3] => [sect1:sect2:sect3] [sect1:sect2:sect3] is now interpreted differently : [sect2] and [sect3] are now both parents of [sect1], [sect3] is not a grandparent here. It is not advised to use multiple inheritance on a regular basis though. """ sections = [] # Find the corresponding section if it inherits from another section sect = self.get_section_name(section) # Section names alone, without parents section_splitted = sect.split(self.section_separator) # Loop to find the abstract parents of the section names without # separators for s in section_splitted: # Search for the abstract parents of the parent sections parents = self._get_corresponding_sections(s) # Get the parents' names without separators full_name = self.get_section_name(s) parents_splitted = full_name.split(self.section_separator) parents_splitted = [x for x in parents_splitted if x not in section_splitted] section_splitted.extend(parents_splitted) # Remove the already present results parents = [x for x in parents if x not in sections] sections.extend(parents) return sections def get_section_name(self, section): """ Returns the actual name of the section inside the read source if it inherits from some other source. /!\\ Only returns the first name found""" if(section in self._sections): return section # Look for the section name or the section name followed by a section # separator for sect in self._sections: if(self.section_separator in sect): subsect = sect[:sect.find(self.section_separator)] else: subsect = sect if (subsect == section): return sect raise NoSectionError(section) def get_section_name_compact(self, section): """ Returns the name of the section without its parents. """ # Splits the name if it has parents or else returns the name itself if(self.section_separator in section): sect = section[:section.find(self.section_separator)] else: sect = section return sect def get_first_section(self): """ Returns the compact name of the first section """ for s in self._sections: s = self.get_section_name_compact(s) return s def get_configs(self, config=''): """ Returns a config name and its direct parents. ex : foo_bar_baz, foo_bar, foo""" configs = [] # Stroes the full config name if(config != '' and config is not None): config_name_edited = config else: config_name_edited = self.config_name # foo_bar_baz and foo_bar here # Splits from the end while(self.config_separator in config_name_edited): configs.append(config_name_edited) config_name_edited = config_name_edited[:config_name_edited.rfind( self.config_separator)] configs.append(config_name_edited) return configs def get_configs_plus(self, config=''): """ Stores and returns the config name itself with all its possible names and all of its possible parents. ex : foo_bar_baz, bar_baz, baz, foo_bar, bar, foo run ex : work on foo_bar_baz: store foo_bar_baz, bar_baz, baz work on foo_bar: store foo_bar, bar work on foo: store foo """ configs = [] if(config != '' and config is not None): config_name_edited = config else: config_name_edited = self.config_name while(self.config_separator in config_name_edited): configs.append(config_name_edited) cfg = config_name_edited while(self.config_separator in cfg): # Remove the first name before the separator # (default : '_') cfg = cfg[cfg.find(self.config_separator)+1:] configs.append(cfg) # Work on the config's parent (baz -> bar) config_name_edited = config_name_edited[:config_name_edited. rfind(self. config_separator)] configs.append(config_name_edited) return configs def convert_value_list(self, val): """ Converts a value into a List if it contains self.list_separator or returns the value in a List if it doesn't. """ list_val = val.split(self.list_separator) return list_val def getint(self, section, option, raw=False, vars=None, fallback=_UNSET): """ Returns the value of an option as an integer. """ res = self.get(section, option, raw, vars, fallback) try: res = int(res) except ValueError: if res is fallback: pass else: raise return res def getfloat(self, section, option, raw=False, vars=None, fallback=_UNSET): """ Returns the value of an option as an double. """ res = self.get(section, option, raw, vars, fallback) try: res = float(res) except ValueError: if res is fallback: pass else: raise return res def getboolean(self, section, option, raw=False, vars=None, fallback=_UNSET): """ Returns the value of an option as a boolean. """ res = self.get(section, option, raw, vars, fallback) try: res = self.str_to_bool(res) except ValueError: if res is fallback: pass else: raise return res def getintlist(self, section, option, raw=False, vars=None, fallback=_UNSET): """ Returns the value of an option as an integer list. """ res = self.get(section, option, raw, vars, fallback, isList=True) try: res = [int(i) for i in res] except ValueError: if res is fallback: pass else: raise return res def getfloatlist(self, section, option, raw=False, vars=None, fallback=_UNSET): """ Returns the value of an option as an double list. """ res = self.get(section, option, raw, vars, fallback, isList=True) try: res = [float(i) for i in res] except ValueError: if res is fallback: pass else: raise return res def getbooleanlist(self, section, option, raw=False, vars=None, fallback=_UNSET): """ Returns the value of an option as a boolean list. """ res = self.get(section, option, raw, vars, fallback, isList=True) try: res = [self.str_to_bool(i) for i in res] except ValueError: if res is fallback: pass else: raise return res def str_to_bool(self, string): """ Returns True if the lowered string is "true", "yes", "on" or "1". Returns False if the lowered string is "false", "no", "off" or "0". Raises ValueError otherwise """ string = string.lower() if(string == 'true' or string == 'yes' or string == 'on' or string == '1'): return True elif(string == 'false' or string == 'no' or string == 'off' or string == '0'): return False else: raise ValueError(string + " is not a boolean") def get_config_name(self): return self.config_name def set_config_name(self, config): self.config_name = config def read(self, filenames, encoding=None): """Read and parse a filename or a list of filenames. Files that cannot be opened are silently ignored; this is designed so that you can specify a list of potential configuration file locations (e.g. current directory, user's home directory, systemwide directory), and all existing configuration files in the list will be read. A single filename may also be given. Return list of successfully read files. """ filenames = u(filenames) super(ExtendedConfigParser, self).read(filenames, encoding) self.move_defaults() def read_file(self, f, source=None): """Like read() but the argument must be a file-like object. The `f' argument must be iterable, returning one line at a time. Optional second argument is the `source' specifying the name of the file being read. If not given, it is taken from f.name. If `f' has no `name' attribute, `' is used. """ super(ExtendedConfigParser, self).read_file(f, source) self.move_defaults() def read_string(self, string, source=''): """ Like read() but the argument must be a string. It is highly recommended that you use unicode strings. """ # Conversion to unicode to ensure Python2 compatibility super(ExtendedConfigParser, self).read_string(string, source) self.move_defaults() def move_defaults(self): """ Transfers the content from the DEFAULT section to self.default_section to prevent get() from returning DEFAULT option values instead of parent option values and "converts" SectionProxies to SectionProxiesExtended. """ try: self.default_section = self._sections['DEFAULT'].copy() del self._sections['DEFAULT'] except KeyError: pass # "Converts" SectionProxies to SectionProxiesExtended for s in self._sections: self._proxies[s] = SectionProxyExtended(self, s) def has_option(self, section, option, config='', cfg_ind=False, strict=False): """ Returns True if the option has been found, returns False otherwise. See the sub-methods' documentation for more details. """ if(strict): if(cfg_ind): return self._has_option_strict_config_ind(section, option) else: return self._has_option_strict(section, option, config) else: if(cfg_ind): return self._has_option_config_ind(section, option) else: return self._has_option(section, option, config) def _has_option(self, section, option, config=''): """ Returns True if the option is explicitly defined in the section or inherited from another section for the current config name (or for the config name precised in the parameters) and returns False otherwise.""" if(self.has_section(section)): sections = self.get_corresponding_sections(section) if(config != '' and config is not None): configs = self.get_configs(config) else: configs = self.get_configs() # Looks for the option in the section and its parents with # different config values for s in sections: for c in configs: res = super(ExtendedConfigParser, self).has_option(s, option + '[' + c + ']') if(res): return True res = super(ExtendedConfigParser, self).has_option(s, option) if(res): return True return False def _has_option_strict(self, section, option, config=''): """ Returns True only if the option is explicitly defined in the section for the current config name (or for the config name precised in the parameters) and returns False otherwise. """ sect = self.get_section_name(section) if(self.has_section(section)): if(config != '' and config is not None): configs = self.get_configs(config) else: configs = self.get_configs() # Looks for the option in the section with different config values for c in configs: res = super(ExtendedConfigParser, self).has_option(sect, option + '[' + c + ']') if(res): return True res = super(ExtendedConfigParser, self).has_option(sect, option) if(res): return True return False def _has_option_config_ind(self, section, option): """ Returns True if the option is explicitly defined in the section or inherited from another section, having a config name specified or not, and returns False otherwise. """ if(self.has_section(section)): sections = self.get_corresponding_sections(section) for s in sections: for o in self._sections[s]: if('[' in o): opt = o[:o.find('[')] else: opt = o if(option == opt): return True return False def _has_option_strict_config_ind(self, section, option): """ Returns True only if the option is explicitly defined in the section, having a config name specified or not, and returns False otherwise. """ if(self.has_section(section)): sect = self.get_section_name(section) for o in self._sections[sect]: if('[' in o): opt = o[:o.find('[')] else: opt = o if(option == opt): return True return False def has_section(self, section, strict=False): """ Returns True if the section name entered is found in the file. If strict is True, it will look for the exact given section name. If strict is False, it will look for the given name with eventual inheritance signs. See the sub-methods' documentation for more details. """ if(strict): return self._has_section_strict(section) else: return self._has_section(section) def _has_section(self, section): """ Checks if there is a section associated with the section name entered. ex : self.has_section('sect1') will return True if 'sect1:sect2' exists """ try: s = self.get_section_name(section) if(s is not None): return True except NoSectionError: return False def _has_section_strict(self, section): """ Checks if the given section name exists. """ return section in self._sections def add_section(self, section): section = u(section) super(ExtendedConfigParser, self).add_section(section) self._proxies[section] = SectionProxyExtended(self, section) def __getitem__(self, key): try: s = self.get_section_name(key) except NoSectionError: raise KeyError(key) return super(ExtendedConfigParser, self).__getitem__(s) def defaults(self): return self.father def items(self, section=_UNSET, raw=False, vars=None, strict=False, defaults=False): """ Returns a list of (name, value) tuples for each option in a section excluding its parents. If defaults is True though, the DEFAULT section will be included. When section is not given, return a list of section_name, section_proxy pairs, including DEFAULTSECT. All % interpolations are expanded in the return values, based on the defaults passed into the constructor, unless the optional argument `raw' is true. Additional substitutions may be provided using the `vars' argument, which must be a dictionary whose contents overrides any pre-existing defaults. The DEFAULT section is special. """ if(strict): return self._items_strict(section, raw, vars, defaults) else: return self._items(section, raw, vars) def _items(self, section=_UNSET, raw=False, vars=None): """ Returns a list of (name, value) tuples for each option in a section and all of its parents. """ res = [] if(section is None or section is _UNSET): res = self._items_empty() else: sections = self.get_corresponding_sections(section) for s in sections: res += (super(ExtendedConfigParser, self).items(s, raw, vars)) defaults = (list(self.default_section.items()) + list(self.father.items())) res += (defaults) return res def _items_strict(self, section=_UNSET, raw=False, vars=None, defaults=False): """ Returns a list of (name, value) tuples for each option in a section excluding its parents. If defaults is True though, the DEFAULT section will be included. """ if(defaults): self._defaults.update(self.father) self._defaults.update(self.default_section) if(section is None or section is _UNSET): res = self._items_empty() else: sect = self.get_section_name(section) res = super(ExtendedConfigParser, self).items(sect, raw, vars) self._defaults = {} return res def _items_empty(self): """ Returns a list of section_name, section_proxy pairs, including DEFAULTSECT. """ return [(sect, self[sect]) for sect in self._sections] def options(self, section, strict=False, defaults=False, cfg_ind=False): """ Returns a list of option names for the given section and its parents if strict is False, or, if strict is True, returns a list of option names for the given section only. If defaults and strict are True, DEFAULT options will be included. If cfg_ind is True, the list will contain the option names without any option specification (without [config_name]) """ if(strict): if(cfg_ind): return self._options_strict_config_ind(section, defaults) else: return self._options_strict(section, defaults) else: if(cfg_ind): return self._options_config_ind(section) else: return self._options(section) def _options(self, section): """ Returns a list of option names for the given section and its parents. """ res = [] sections = self.get_corresponding_sections(section) for s in sections: res += super(ExtendedConfigParser, self).options(s) if(self.default_section is not None): res += list(self.default_section.keys()) if(self.father is not None): res += list(self.father.keys()) return res def _options_strict(self, section, defaults=False): """ Returns a list of option names for the given section only. If defaults is True, DEFAULT options will be included. """ sect = self.get_section_name(section) res = super(ExtendedConfigParser, self).options(sect) if(defaults): if(self.default_section is not None): res += list(self.default_section.keys()) if(self.father is not None): res += list(self.father.keys()) return res def _options_config_ind(self, section): """ Returns a list of option names without the option specifications for the given section and its parents. """ res = [] sections = self.get_corresponding_sections(section) for s in sections: # Only the elements that are not in common are taken res += list(set(self._options_strict_config_ind(s, defaults=False)) - set(res)) return res def _options_strict_config_ind(self, section, defaults=False): """ Returns a list of option names without the config specifications for the given section only. If defaults is True, DEFAULT options will be included. """ res = [] sect = self.get_section_name(section) options = super(ExtendedConfigParser, self).options(sect) if(defaults): if(self.default_section is not None): options += list(self.default_section.keys()) if(self.father is not None): options += list(self.father.keys()) for o in options: if("[" in o): opt = o[:o.find("[")] else: opt = o if(opt not in res): res.append(opt) return res def set_config_separator(self, separator): self.config_separator = separator def set_section_separator(self, separator): self.section_separator = separator def set_list_separator(self, separator): self.list_separator = separator def set_inheritance(self, inheritance): self.inheritance = inheritance class SectionProxyExtended(configparser.SectionProxy): """ A proxy for a single section from a parser. Designed to support inheritance. """ def __init__(self, parser, name): self._parser = parser self._name = name def __getitem__(self, key): try: return self._parser.get(self._name, key) except NoOptionError: raise KeyError(key)