eis/py/comlib/configparser_extended/ecp.py

954 lines
36 KiB
Python
Raw Normal View History

# -*- 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='<string>'):
""" 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)