Issue #5412: extend configparser to support mapping access
This commit is contained in:
parent
47f637ce17
commit
26d513cf2f
File diff suppressed because it is too large
Load Diff
|
@ -85,7 +85,7 @@ ConfigParser -- responsible for parsing a list of
|
||||||
and their keys will be added in order. Values are automatically
|
and their keys will be added in order. Values are automatically
|
||||||
converted to strings.
|
converted to strings.
|
||||||
|
|
||||||
get(section, option, raw=False, vars=None, default=_UNSET)
|
get(section, option, raw=False, vars=None, fallback=_UNSET)
|
||||||
Return a string value for the named option. All % interpolations are
|
Return a string value for the named option. All % interpolations are
|
||||||
expanded in the return values, based on the defaults passed into the
|
expanded in the return values, based on the defaults passed into the
|
||||||
constructor and the DEFAULT section. Additional substitutions may be
|
constructor and the DEFAULT section. Additional substitutions may be
|
||||||
|
@ -93,13 +93,13 @@ ConfigParser -- responsible for parsing a list of
|
||||||
contents override any pre-existing defaults. If `option' is a key in
|
contents override any pre-existing defaults. If `option' is a key in
|
||||||
`vars', the value from `vars' is used.
|
`vars', the value from `vars' is used.
|
||||||
|
|
||||||
getint(section, options, raw=False, vars=None, default=_UNSET)
|
getint(section, options, raw=False, vars=None, fallback=_UNSET)
|
||||||
Like get(), but convert value to an integer.
|
Like get(), but convert value to an integer.
|
||||||
|
|
||||||
getfloat(section, options, raw=False, vars=None, default=_UNSET)
|
getfloat(section, options, raw=False, vars=None, fallback=_UNSET)
|
||||||
Like get(), but convert value to a float.
|
Like get(), but convert value to a float.
|
||||||
|
|
||||||
getboolean(section, options, raw=False, vars=None, default=_UNSET)
|
getboolean(section, options, raw=False, vars=None, fallback=_UNSET)
|
||||||
Like get(), but convert value to a boolean (currently case
|
Like get(), but convert value to a boolean (currently case
|
||||||
insensitively defined as 0, false, no, off for False, and 1, true,
|
insensitively defined as 0, false, no, off for False, and 1, true,
|
||||||
yes, on for True). Returns False or True.
|
yes, on for True). Returns False or True.
|
||||||
|
@ -123,13 +123,10 @@ ConfigParser -- responsible for parsing a list of
|
||||||
between keys and values are surrounded by spaces.
|
between keys and values are surrounded by spaces.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
from collections import MutableMapping, OrderedDict as _default_dict
|
||||||
from collections import OrderedDict as _default_dict
|
import functools
|
||||||
except ImportError:
|
|
||||||
# fallback for setup.py which hasn't yet built _collections
|
|
||||||
_default_dict = dict
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import itertools
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -366,7 +363,7 @@ _COMPATIBLE = object()
|
||||||
_UNSET = object()
|
_UNSET = object()
|
||||||
|
|
||||||
|
|
||||||
class RawConfigParser:
|
class RawConfigParser(MutableMapping):
|
||||||
"""ConfigParser that does not do interpolation."""
|
"""ConfigParser that does not do interpolation."""
|
||||||
|
|
||||||
# Regular expressions for parsing section headers and options
|
# Regular expressions for parsing section headers and options
|
||||||
|
@ -413,6 +410,8 @@ class RawConfigParser:
|
||||||
self._dict = dict_type
|
self._dict = dict_type
|
||||||
self._sections = self._dict()
|
self._sections = self._dict()
|
||||||
self._defaults = self._dict()
|
self._defaults = self._dict()
|
||||||
|
self._views = self._dict()
|
||||||
|
self._views[DEFAULTSECT] = SectionProxy(self, DEFAULTSECT)
|
||||||
if defaults:
|
if defaults:
|
||||||
for key, value in defaults.items():
|
for key, value in defaults.items():
|
||||||
self._defaults[self.optionxform(key)] = value
|
self._defaults[self.optionxform(key)] = value
|
||||||
|
@ -434,6 +433,7 @@ class RawConfigParser:
|
||||||
self._startonly_comment_prefixes = ()
|
self._startonly_comment_prefixes = ()
|
||||||
self._comment_prefixes = tuple(comment_prefixes or ())
|
self._comment_prefixes = tuple(comment_prefixes or ())
|
||||||
self._strict = strict
|
self._strict = strict
|
||||||
|
self._allow_no_value = allow_no_value
|
||||||
self._empty_lines_in_values = empty_lines_in_values
|
self._empty_lines_in_values = empty_lines_in_values
|
||||||
|
|
||||||
def defaults(self):
|
def defaults(self):
|
||||||
|
@ -451,12 +451,13 @@ class RawConfigParser:
|
||||||
already exists. Raise ValueError if name is DEFAULT or any of it's
|
already exists. Raise ValueError if name is DEFAULT or any of it's
|
||||||
case-insensitive variants.
|
case-insensitive variants.
|
||||||
"""
|
"""
|
||||||
if section.lower() == "default":
|
if section.upper() == DEFAULTSECT:
|
||||||
raise ValueError('Invalid section name: %s' % section)
|
raise ValueError('Invalid section name: %s' % section)
|
||||||
|
|
||||||
if section in self._sections:
|
if section in self._sections:
|
||||||
raise DuplicateSectionError(section)
|
raise DuplicateSectionError(section)
|
||||||
self._sections[section] = self._dict()
|
self._sections[section] = self._dict()
|
||||||
|
self._views[section] = SectionProxy(self, section)
|
||||||
|
|
||||||
def has_section(self, section):
|
def has_section(self, section):
|
||||||
"""Indicate whether the named section is present in the configuration.
|
"""Indicate whether the named section is present in the configuration.
|
||||||
|
@ -534,7 +535,7 @@ class RawConfigParser:
|
||||||
for section, keys in dictionary.items():
|
for section, keys in dictionary.items():
|
||||||
try:
|
try:
|
||||||
self.add_section(section)
|
self.add_section(section)
|
||||||
except DuplicateSectionError:
|
except (DuplicateSectionError, ValueError):
|
||||||
if self._strict and section in elements_added:
|
if self._strict and section in elements_added:
|
||||||
raise
|
raise
|
||||||
elements_added.add(section)
|
elements_added.add(section)
|
||||||
|
@ -556,29 +557,31 @@ class RawConfigParser:
|
||||||
)
|
)
|
||||||
self.read_file(fp, source=filename)
|
self.read_file(fp, source=filename)
|
||||||
|
|
||||||
def get(self, section, option, vars=None, default=_UNSET):
|
def get(self, section, option, *, vars=None, fallback=_UNSET):
|
||||||
"""Get an option value for a given section.
|
"""Get an option value for a given section.
|
||||||
|
|
||||||
If `vars' is provided, it must be a dictionary. The option is looked up
|
If `vars' is provided, it must be a dictionary. The option is looked up
|
||||||
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
|
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
|
||||||
If the key is not found and `default' is provided, it is used as
|
If the key is not found and `fallback' is provided, it is used as
|
||||||
a fallback value. `None' can be provided as a `default' value.
|
a fallback value. `None' can be provided as a `fallback' value.
|
||||||
|
|
||||||
|
Arguments `vars' and `fallback' are keyword only.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
d = self._unify_values(section, vars)
|
d = self._unify_values(section, vars)
|
||||||
except NoSectionError:
|
except NoSectionError:
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
option = self.optionxform(option)
|
option = self.optionxform(option)
|
||||||
try:
|
try:
|
||||||
return d[option]
|
return d[option]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise NoOptionError(option, section)
|
raise NoOptionError(option, section)
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
|
|
||||||
def items(self, section):
|
def items(self, section):
|
||||||
try:
|
try:
|
||||||
|
@ -593,35 +596,36 @@ class RawConfigParser:
|
||||||
del d["__name__"]
|
del d["__name__"]
|
||||||
return d.items()
|
return d.items()
|
||||||
|
|
||||||
def _get(self, section, conv, option, *args, **kwargs):
|
def _get(self, section, conv, option, **kwargs):
|
||||||
return conv(self.get(section, option, *args, **kwargs))
|
return conv(self.get(section, option, **kwargs))
|
||||||
|
|
||||||
def getint(self, section, option, vars=None, default=_UNSET):
|
def getint(self, section, option, *, vars=None, fallback=_UNSET):
|
||||||
try:
|
try:
|
||||||
return self._get(section, int, option, vars)
|
return self._get(section, int, option, vars=vars)
|
||||||
except (NoSectionError, NoOptionError):
|
except (NoSectionError, NoOptionError):
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
|
|
||||||
def getfloat(self, section, option, vars=None, default=_UNSET):
|
def getfloat(self, section, option, *, vars=None, fallback=_UNSET):
|
||||||
try:
|
try:
|
||||||
return self._get(section, float, option, vars)
|
return self._get(section, float, option, vars=vars)
|
||||||
except (NoSectionError, NoOptionError):
|
except (NoSectionError, NoOptionError):
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
|
|
||||||
def getboolean(self, section, option, vars=None, default=_UNSET):
|
def getboolean(self, section, option, *, vars=None, fallback=_UNSET):
|
||||||
try:
|
try:
|
||||||
return self._get(section, self._convert_to_boolean, option, vars)
|
return self._get(section, self._convert_to_boolean, option,
|
||||||
|
vars=vars)
|
||||||
except (NoSectionError, NoOptionError):
|
except (NoSectionError, NoOptionError):
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
|
|
||||||
def optionxform(self, optionstr):
|
def optionxform(self, optionstr):
|
||||||
return optionstr.lower()
|
return optionstr.lower()
|
||||||
|
@ -671,7 +675,7 @@ class RawConfigParser:
|
||||||
for key, value in section_items:
|
for key, value in section_items:
|
||||||
if key == "__name__":
|
if key == "__name__":
|
||||||
continue
|
continue
|
||||||
if (value is not None) or (self._optcre == self.OPTCRE):
|
if value is not None or not self._allow_no_value:
|
||||||
value = delimiter + str(value).replace('\n', '\n\t')
|
value = delimiter + str(value).replace('\n', '\n\t')
|
||||||
else:
|
else:
|
||||||
value = ""
|
value = ""
|
||||||
|
@ -698,8 +702,40 @@ class RawConfigParser:
|
||||||
existed = section in self._sections
|
existed = section in self._sections
|
||||||
if existed:
|
if existed:
|
||||||
del self._sections[section]
|
del self._sections[section]
|
||||||
|
del self._views[section]
|
||||||
return existed
|
return existed
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key != DEFAULTSECT and not self.has_section(key):
|
||||||
|
raise KeyError(key)
|
||||||
|
return self._views[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# To conform with the mapping protocol, overwrites existing values in
|
||||||
|
# the section.
|
||||||
|
|
||||||
|
# XXX this is not atomic if read_dict fails at any point. Then again,
|
||||||
|
# no update method in configparser is atomic in this implementation.
|
||||||
|
self.remove_section(key)
|
||||||
|
self.read_dict({key: value})
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
if key == DEFAULTSECT:
|
||||||
|
raise ValueError("Cannot remove the default section.")
|
||||||
|
if not self.has_section(key):
|
||||||
|
raise KeyError(key)
|
||||||
|
self.remove_section(key)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key == DEFAULTSECT or self.has_section(key)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._sections) + 1 # the default section
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
# XXX does it break when underlying container state changed?
|
||||||
|
return itertools.chain((DEFAULTSECT,), self._sections.keys())
|
||||||
|
|
||||||
def _read(self, fp, fpname):
|
def _read(self, fp, fpname):
|
||||||
"""Parse a sectioned configuration file.
|
"""Parse a sectioned configuration file.
|
||||||
|
|
||||||
|
@ -776,6 +812,7 @@ class RawConfigParser:
|
||||||
cursect = self._dict()
|
cursect = self._dict()
|
||||||
cursect['__name__'] = sectname
|
cursect['__name__'] = sectname
|
||||||
self._sections[sectname] = cursect
|
self._sections[sectname] = cursect
|
||||||
|
self._views[sectname] = SectionProxy(self, sectname)
|
||||||
elements_added.add(sectname)
|
elements_added.add(sectname)
|
||||||
# So sections can't start with a continuation line
|
# So sections can't start with a continuation line
|
||||||
optname = None
|
optname = None
|
||||||
|
@ -818,8 +855,8 @@ class RawConfigParser:
|
||||||
self._join_multiline_values()
|
self._join_multiline_values()
|
||||||
|
|
||||||
def _join_multiline_values(self):
|
def _join_multiline_values(self):
|
||||||
all_sections = [self._defaults]
|
all_sections = itertools.chain((self._defaults,),
|
||||||
all_sections.extend(self._sections.values())
|
self._sections.values())
|
||||||
for options in all_sections:
|
for options in all_sections:
|
||||||
for name, val in options.items():
|
for name, val in options.items():
|
||||||
if isinstance(val, list):
|
if isinstance(val, list):
|
||||||
|
@ -857,73 +894,95 @@ class RawConfigParser:
|
||||||
raise ValueError('Not a boolean: %s' % value)
|
raise ValueError('Not a boolean: %s' % value)
|
||||||
return self.BOOLEAN_STATES[value.lower()]
|
return self.BOOLEAN_STATES[value.lower()]
|
||||||
|
|
||||||
|
def _validate_value_type(self, value):
|
||||||
|
"""Raises a TypeError for non-string values.
|
||||||
|
|
||||||
|
The only legal non-string value if we allow valueless
|
||||||
|
options is None, so we need to check if the value is a
|
||||||
|
string if:
|
||||||
|
- we do not allow valueless options, or
|
||||||
|
- we allow valueless options but the value is not None
|
||||||
|
|
||||||
|
For compatibility reasons this method is not used in classic set()
|
||||||
|
for RawConfigParsers and ConfigParsers. It is invoked in every
|
||||||
|
case for mapping protocol access and in SafeConfigParser.set().
|
||||||
|
"""
|
||||||
|
if not self._allow_no_value or value:
|
||||||
|
if not isinstance(value, str):
|
||||||
|
raise TypeError("option values must be strings")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigParser(RawConfigParser):
|
class ConfigParser(RawConfigParser):
|
||||||
"""ConfigParser implementing interpolation."""
|
"""ConfigParser implementing interpolation."""
|
||||||
|
|
||||||
def get(self, section, option, raw=False, vars=None, default=_UNSET):
|
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
|
||||||
"""Get an option value for a given section.
|
"""Get an option value for a given section.
|
||||||
|
|
||||||
If `vars' is provided, it must be a dictionary. The option is looked up
|
If `vars' is provided, it must be a dictionary. The option is looked up
|
||||||
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
|
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
|
||||||
If the key is not found and `default' is provided, it is used as
|
If the key is not found and `fallback' is provided, it is used as
|
||||||
a fallback value. `None' can be provided as a `default' value.
|
a fallback value. `None' can be provided as a `fallback' value.
|
||||||
|
|
||||||
All % interpolations are expanded in the return values, unless the
|
All % interpolations are expanded in the return values, unless the
|
||||||
optional argument `raw' is true. Values for interpolation keys are
|
optional argument `raw' is true. Values for interpolation keys are
|
||||||
looked up in the same manner as the option.
|
looked up in the same manner as the option.
|
||||||
|
|
||||||
|
Arguments `raw', `vars', and `fallback' are keyword only.
|
||||||
|
|
||||||
The section DEFAULT is special.
|
The section DEFAULT is special.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
d = self._unify_values(section, vars)
|
d = self._unify_values(section, vars)
|
||||||
except NoSectionError:
|
except NoSectionError:
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
option = self.optionxform(option)
|
option = self.optionxform(option)
|
||||||
try:
|
try:
|
||||||
value = d[option]
|
value = d[option]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise NoOptionError(option, section)
|
raise NoOptionError(option, section)
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
|
|
||||||
if raw or value is None:
|
if raw or value is None:
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
return self._interpolate(section, option, value, d)
|
return self._interpolate(section, option, value, d)
|
||||||
|
|
||||||
def getint(self, section, option, raw=False, vars=None, default=_UNSET):
|
def getint(self, section, option, *, raw=False, vars=None,
|
||||||
|
fallback=_UNSET):
|
||||||
try:
|
try:
|
||||||
return self._get(section, int, option, raw, vars)
|
return self._get(section, int, option, raw=raw, vars=vars)
|
||||||
except (NoSectionError, NoOptionError):
|
except (NoSectionError, NoOptionError):
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
|
|
||||||
def getfloat(self, section, option, raw=False, vars=None, default=_UNSET):
|
def getfloat(self, section, option, *, raw=False, vars=None,
|
||||||
|
fallback=_UNSET):
|
||||||
try:
|
try:
|
||||||
return self._get(section, float, option, raw, vars)
|
return self._get(section, float, option, raw=raw, vars=vars)
|
||||||
except (NoSectionError, NoOptionError):
|
except (NoSectionError, NoOptionError):
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
|
|
||||||
def getboolean(self, section, option, raw=False, vars=None,
|
def getboolean(self, section, option, *, raw=False, vars=None,
|
||||||
default=_UNSET):
|
fallback=_UNSET):
|
||||||
try:
|
try:
|
||||||
return self._get(section, self._convert_to_boolean, option, raw,
|
return self._get(section, self._convert_to_boolean, option,
|
||||||
vars)
|
raw=raw, vars=vars)
|
||||||
except (NoSectionError, NoOptionError):
|
except (NoSectionError, NoOptionError):
|
||||||
if default is _UNSET:
|
if fallback is _UNSET:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return default
|
return fallback
|
||||||
|
|
||||||
def items(self, section, raw=False, vars=None):
|
def items(self, section, raw=False, vars=None):
|
||||||
"""Return a list of (name, value) tuples for each option in a section.
|
"""Return a list of (name, value) tuples for each option in a section.
|
||||||
|
@ -1037,14 +1096,7 @@ class SafeConfigParser(ConfigParser):
|
||||||
|
|
||||||
def set(self, section, option, value=None):
|
def set(self, section, option, value=None):
|
||||||
"""Set an option. Extend ConfigParser.set: check for string values."""
|
"""Set an option. Extend ConfigParser.set: check for string values."""
|
||||||
# The only legal non-string value if we allow valueless
|
self._validate_value_type(value)
|
||||||
# options is None, so we need to check if the value is a
|
|
||||||
# string if:
|
|
||||||
# - we do not allow valueless options, or
|
|
||||||
# - we allow valueless options but the value is not None
|
|
||||||
if self._optcre is self.OPTCRE or value:
|
|
||||||
if not isinstance(value, str):
|
|
||||||
raise TypeError("option values must be strings")
|
|
||||||
# check for bad percent signs
|
# check for bad percent signs
|
||||||
if value:
|
if value:
|
||||||
tmp_value = value.replace('%%', '') # escaped percent signs
|
tmp_value = value.replace('%%', '') # escaped percent signs
|
||||||
|
@ -1053,3 +1105,60 @@ class SafeConfigParser(ConfigParser):
|
||||||
raise ValueError("invalid interpolation syntax in %r at "
|
raise ValueError("invalid interpolation syntax in %r at "
|
||||||
"position %d" % (value, tmp_value.find('%')))
|
"position %d" % (value, tmp_value.find('%')))
|
||||||
ConfigParser.set(self, section, option, value)
|
ConfigParser.set(self, section, option, value)
|
||||||
|
|
||||||
|
|
||||||
|
class SectionProxy(MutableMapping):
|
||||||
|
"""A proxy for a single section from a parser."""
|
||||||
|
|
||||||
|
_noname = ("__name__ special key access and modification "
|
||||||
|
"not supported through the mapping interface.")
|
||||||
|
|
||||||
|
def __init__(self, parser, section_name):
|
||||||
|
"""Creates a view on a section named `section_name` in `parser`."""
|
||||||
|
self._parser = parser
|
||||||
|
self._section = section_name
|
||||||
|
self.getint = functools.partial(self._parser.getint,
|
||||||
|
self._section)
|
||||||
|
self.getfloat = functools.partial(self._parser.getfloat,
|
||||||
|
self._section)
|
||||||
|
self.getboolean = functools.partial(self._parser.getboolean,
|
||||||
|
self._section)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Section: {}>'.format(self._section)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key == '__name__':
|
||||||
|
raise ValueError(self._noname)
|
||||||
|
if not self._parser.has_option(self._section, key):
|
||||||
|
raise KeyError(key)
|
||||||
|
return self._parser.get(self._section, key)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key == '__name__':
|
||||||
|
raise ValueError(self._noname)
|
||||||
|
self._parser._validate_value_type(value)
|
||||||
|
return self._parser.set(self._section, key, value)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
if key == '__name__':
|
||||||
|
raise ValueError(self._noname)
|
||||||
|
if not self._parser.has_option(self._section, key):
|
||||||
|
raise KeyError(key)
|
||||||
|
return self._parser.remove_option(self._section, key)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
if key == '__name__':
|
||||||
|
return False
|
||||||
|
return self._parser.has_option(self._section, key)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
# __name__ is properly hidden by .options()
|
||||||
|
# XXX weak performance
|
||||||
|
return len(self._parser.options(self._section))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
# __name__ is properly hidden by .options()
|
||||||
|
# XXX weak performance
|
||||||
|
# XXX does not break when underlying container state changed
|
||||||
|
return self._parser.options(self._section).__iter__()
|
||||||
|
|
|
@ -103,7 +103,7 @@ def _encoded(s):
|
||||||
|
|
||||||
def _create_formatters(cp):
|
def _create_formatters(cp):
|
||||||
"""Create and return formatters"""
|
"""Create and return formatters"""
|
||||||
flist = cp.get("formatters", "keys")
|
flist = cp["formatters"]["keys"]
|
||||||
if not len(flist):
|
if not len(flist):
|
||||||
return {}
|
return {}
|
||||||
flist = flist.split(",")
|
flist = flist.split(",")
|
||||||
|
@ -111,20 +111,12 @@ def _create_formatters(cp):
|
||||||
formatters = {}
|
formatters = {}
|
||||||
for form in flist:
|
for form in flist:
|
||||||
sectname = "formatter_%s" % form
|
sectname = "formatter_%s" % form
|
||||||
opts = cp.options(sectname)
|
fs = cp.get(sectname, "format", raw=True, fallback=None)
|
||||||
if "format" in opts:
|
dfs = cp.get(sectname, "datefmt", raw=True, fallback=None)
|
||||||
fs = cp.get(sectname, "format", 1)
|
|
||||||
else:
|
|
||||||
fs = None
|
|
||||||
if "datefmt" in opts:
|
|
||||||
dfs = cp.get(sectname, "datefmt", 1)
|
|
||||||
else:
|
|
||||||
dfs = None
|
|
||||||
c = logging.Formatter
|
c = logging.Formatter
|
||||||
if "class" in opts:
|
class_name = cp[sectname].get("class")
|
||||||
class_name = cp.get(sectname, "class")
|
if class_name:
|
||||||
if class_name:
|
c = _resolve(class_name)
|
||||||
c = _resolve(class_name)
|
|
||||||
f = c(fs, dfs)
|
f = c(fs, dfs)
|
||||||
formatters[form] = f
|
formatters[form] = f
|
||||||
return formatters
|
return formatters
|
||||||
|
@ -132,7 +124,7 @@ def _create_formatters(cp):
|
||||||
|
|
||||||
def _install_handlers(cp, formatters):
|
def _install_handlers(cp, formatters):
|
||||||
"""Install and return handlers"""
|
"""Install and return handlers"""
|
||||||
hlist = cp.get("handlers", "keys")
|
hlist = cp["handlers"]["keys"]
|
||||||
if not len(hlist):
|
if not len(hlist):
|
||||||
return {}
|
return {}
|
||||||
hlist = hlist.split(",")
|
hlist = hlist.split(",")
|
||||||
|
@ -140,30 +132,23 @@ def _install_handlers(cp, formatters):
|
||||||
handlers = {}
|
handlers = {}
|
||||||
fixups = [] #for inter-handler references
|
fixups = [] #for inter-handler references
|
||||||
for hand in hlist:
|
for hand in hlist:
|
||||||
sectname = "handler_%s" % hand
|
section = cp["handler_%s" % hand]
|
||||||
klass = cp.get(sectname, "class")
|
klass = section["class"]
|
||||||
opts = cp.options(sectname)
|
fmt = section.get("formatter", "")
|
||||||
if "formatter" in opts:
|
|
||||||
fmt = cp.get(sectname, "formatter")
|
|
||||||
else:
|
|
||||||
fmt = ""
|
|
||||||
try:
|
try:
|
||||||
klass = eval(klass, vars(logging))
|
klass = eval(klass, vars(logging))
|
||||||
except (AttributeError, NameError):
|
except (AttributeError, NameError):
|
||||||
klass = _resolve(klass)
|
klass = _resolve(klass)
|
||||||
args = cp.get(sectname, "args")
|
args = section["args"]
|
||||||
args = eval(args, vars(logging))
|
args = eval(args, vars(logging))
|
||||||
h = klass(*args)
|
h = klass(*args)
|
||||||
if "level" in opts:
|
if "level" in section:
|
||||||
level = cp.get(sectname, "level")
|
level = section["level"]
|
||||||
h.setLevel(logging._levelNames[level])
|
h.setLevel(logging._levelNames[level])
|
||||||
if len(fmt):
|
if len(fmt):
|
||||||
h.setFormatter(formatters[fmt])
|
h.setFormatter(formatters[fmt])
|
||||||
if issubclass(klass, logging.handlers.MemoryHandler):
|
if issubclass(klass, logging.handlers.MemoryHandler):
|
||||||
if "target" in opts:
|
target = section.get("target", "")
|
||||||
target = cp.get(sectname,"target")
|
|
||||||
else:
|
|
||||||
target = ""
|
|
||||||
if len(target): #the target handler may not be loaded yet, so keep for later...
|
if len(target): #the target handler may not be loaded yet, so keep for later...
|
||||||
fixups.append((h, target))
|
fixups.append((h, target))
|
||||||
handlers[hand] = h
|
handlers[hand] = h
|
||||||
|
@ -197,20 +182,19 @@ def _install_loggers(cp, handlers, disable_existing):
|
||||||
"""Create and install loggers"""
|
"""Create and install loggers"""
|
||||||
|
|
||||||
# configure the root first
|
# configure the root first
|
||||||
llist = cp.get("loggers", "keys")
|
llist = cp["loggers"]["keys"]
|
||||||
llist = llist.split(",")
|
llist = llist.split(",")
|
||||||
llist = list(map(lambda x: x.strip(), llist))
|
llist = list(map(lambda x: x.strip(), llist))
|
||||||
llist.remove("root")
|
llist.remove("root")
|
||||||
sectname = "logger_root"
|
section = cp["logger_root"]
|
||||||
root = logging.root
|
root = logging.root
|
||||||
log = root
|
log = root
|
||||||
opts = cp.options(sectname)
|
if "level" in section:
|
||||||
if "level" in opts:
|
level = section["level"]
|
||||||
level = cp.get(sectname, "level")
|
|
||||||
log.setLevel(logging._levelNames[level])
|
log.setLevel(logging._levelNames[level])
|
||||||
for h in root.handlers[:]:
|
for h in root.handlers[:]:
|
||||||
root.removeHandler(h)
|
root.removeHandler(h)
|
||||||
hlist = cp.get(sectname, "handlers")
|
hlist = section["handlers"]
|
||||||
if len(hlist):
|
if len(hlist):
|
||||||
hlist = hlist.split(",")
|
hlist = hlist.split(",")
|
||||||
hlist = _strip_spaces(hlist)
|
hlist = _strip_spaces(hlist)
|
||||||
|
@ -237,13 +221,9 @@ def _install_loggers(cp, handlers, disable_existing):
|
||||||
child_loggers = []
|
child_loggers = []
|
||||||
#now set up the new ones...
|
#now set up the new ones...
|
||||||
for log in llist:
|
for log in llist:
|
||||||
sectname = "logger_%s" % log
|
section = cp["logger_%s" % log]
|
||||||
qn = cp.get(sectname, "qualname")
|
qn = section["qualname"]
|
||||||
opts = cp.options(sectname)
|
propagate = section.getint("propagate", fallback=1)
|
||||||
if "propagate" in opts:
|
|
||||||
propagate = cp.getint(sectname, "propagate")
|
|
||||||
else:
|
|
||||||
propagate = 1
|
|
||||||
logger = logging.getLogger(qn)
|
logger = logging.getLogger(qn)
|
||||||
if qn in existing:
|
if qn in existing:
|
||||||
i = existing.index(qn)
|
i = existing.index(qn)
|
||||||
|
@ -255,14 +235,14 @@ def _install_loggers(cp, handlers, disable_existing):
|
||||||
child_loggers.append(existing[i])
|
child_loggers.append(existing[i])
|
||||||
i = i + 1
|
i = i + 1
|
||||||
existing.remove(qn)
|
existing.remove(qn)
|
||||||
if "level" in opts:
|
if "level" in section:
|
||||||
level = cp.get(sectname, "level")
|
level = section["level"]
|
||||||
logger.setLevel(logging._levelNames[level])
|
logger.setLevel(logging._levelNames[level])
|
||||||
for h in logger.handlers[:]:
|
for h in logger.handlers[:]:
|
||||||
logger.removeHandler(h)
|
logger.removeHandler(h)
|
||||||
logger.propagate = propagate
|
logger.propagate = propagate
|
||||||
logger.disabled = 0
|
logger.disabled = 0
|
||||||
hlist = cp.get(sectname, "handlers")
|
hlist = section["handlers"]
|
||||||
if len(hlist):
|
if len(hlist):
|
||||||
hlist = hlist.split(",")
|
hlist = hlist.split(",")
|
||||||
hlist = _strip_spaces(hlist)
|
hlist = _strip_spaces(hlist)
|
||||||
|
|
|
@ -52,8 +52,6 @@ class CfgParserTestCaseClass(unittest.TestCase):
|
||||||
class BasicTestCase(CfgParserTestCaseClass):
|
class BasicTestCase(CfgParserTestCaseClass):
|
||||||
|
|
||||||
def basic_test(self, cf):
|
def basic_test(self, cf):
|
||||||
L = cf.sections()
|
|
||||||
L.sort()
|
|
||||||
E = ['Commented Bar',
|
E = ['Commented Bar',
|
||||||
'Foo Bar',
|
'Foo Bar',
|
||||||
'Internationalized Stuff',
|
'Internationalized Stuff',
|
||||||
|
@ -64,20 +62,34 @@ class BasicTestCase(CfgParserTestCaseClass):
|
||||||
'Spacey Bar From The Beginning',
|
'Spacey Bar From The Beginning',
|
||||||
'Types',
|
'Types',
|
||||||
]
|
]
|
||||||
|
|
||||||
if self.allow_no_value:
|
if self.allow_no_value:
|
||||||
E.append('NoValue')
|
E.append('NoValue')
|
||||||
E.sort()
|
E.sort()
|
||||||
|
|
||||||
|
# API access
|
||||||
|
L = cf.sections()
|
||||||
|
L.sort()
|
||||||
eq = self.assertEqual
|
eq = self.assertEqual
|
||||||
eq(L, E)
|
eq(L, E)
|
||||||
|
|
||||||
|
# mapping access
|
||||||
|
L = [section for section in cf]
|
||||||
|
L.sort()
|
||||||
|
E.append(configparser.DEFAULTSECT)
|
||||||
|
E.sort()
|
||||||
|
eq(L, E)
|
||||||
|
|
||||||
# The use of spaces in the section names serves as a
|
# The use of spaces in the section names serves as a
|
||||||
# regression test for SourceForge bug #583248:
|
# regression test for SourceForge bug #583248:
|
||||||
# http://www.python.org/sf/583248
|
# http://www.python.org/sf/583248
|
||||||
eq(cf.get('Foo Bar', 'foo'), 'bar')
|
|
||||||
eq(cf.get('Spacey Bar', 'foo'), 'bar')
|
# API access
|
||||||
eq(cf.get('Spacey Bar From The Beginning', 'foo'), 'bar')
|
eq(cf.get('Foo Bar', 'foo'), 'bar1')
|
||||||
|
eq(cf.get('Spacey Bar', 'foo'), 'bar2')
|
||||||
|
eq(cf.get('Spacey Bar From The Beginning', 'foo'), 'bar3')
|
||||||
eq(cf.get('Spacey Bar From The Beginning', 'baz'), 'qwe')
|
eq(cf.get('Spacey Bar From The Beginning', 'baz'), 'qwe')
|
||||||
eq(cf.get('Commented Bar', 'foo'), 'bar')
|
eq(cf.get('Commented Bar', 'foo'), 'bar4')
|
||||||
eq(cf.get('Commented Bar', 'baz'), 'qwe')
|
eq(cf.get('Commented Bar', 'baz'), 'qwe')
|
||||||
eq(cf.get('Spaces', 'key with spaces'), 'value')
|
eq(cf.get('Spaces', 'key with spaces'), 'value')
|
||||||
eq(cf.get('Spaces', 'another with spaces'), 'splat!')
|
eq(cf.get('Spaces', 'another with spaces'), 'splat!')
|
||||||
|
@ -89,40 +101,69 @@ class BasicTestCase(CfgParserTestCaseClass):
|
||||||
if self.allow_no_value:
|
if self.allow_no_value:
|
||||||
eq(cf.get('NoValue', 'option-without-value'), None)
|
eq(cf.get('NoValue', 'option-without-value'), None)
|
||||||
|
|
||||||
# test vars= and default=
|
# test vars= and fallback=
|
||||||
eq(cf.get('Foo Bar', 'foo', default='baz'), 'bar')
|
eq(cf.get('Foo Bar', 'foo', fallback='baz'), 'bar1')
|
||||||
eq(cf.get('Foo Bar', 'foo', vars={'foo': 'baz'}), 'baz')
|
eq(cf.get('Foo Bar', 'foo', vars={'foo': 'baz'}), 'baz')
|
||||||
with self.assertRaises(configparser.NoSectionError):
|
with self.assertRaises(configparser.NoSectionError):
|
||||||
cf.get('No Such Foo Bar', 'foo')
|
cf.get('No Such Foo Bar', 'foo')
|
||||||
with self.assertRaises(configparser.NoOptionError):
|
with self.assertRaises(configparser.NoOptionError):
|
||||||
cf.get('Foo Bar', 'no-such-foo')
|
cf.get('Foo Bar', 'no-such-foo')
|
||||||
eq(cf.get('No Such Foo Bar', 'foo', default='baz'), 'baz')
|
eq(cf.get('No Such Foo Bar', 'foo', fallback='baz'), 'baz')
|
||||||
eq(cf.get('Foo Bar', 'no-such-foo', default='baz'), 'baz')
|
eq(cf.get('Foo Bar', 'no-such-foo', fallback='baz'), 'baz')
|
||||||
eq(cf.get('Spacey Bar', 'foo', default=None), 'bar')
|
eq(cf.get('Spacey Bar', 'foo', fallback=None), 'bar2')
|
||||||
eq(cf.get('No Such Spacey Bar', 'foo', default=None), None)
|
eq(cf.get('No Such Spacey Bar', 'foo', fallback=None), None)
|
||||||
eq(cf.getint('Types', 'int', default=18), 42)
|
eq(cf.getint('Types', 'int', fallback=18), 42)
|
||||||
eq(cf.getint('Types', 'no-such-int', default=18), 18)
|
eq(cf.getint('Types', 'no-such-int', fallback=18), 18)
|
||||||
eq(cf.getint('Types', 'no-such-int', default="18"), "18") # sic!
|
eq(cf.getint('Types', 'no-such-int', fallback="18"), "18") # sic!
|
||||||
self.assertAlmostEqual(cf.getfloat('Types', 'float',
|
self.assertAlmostEqual(cf.getfloat('Types', 'float',
|
||||||
default=0.0), 0.44)
|
fallback=0.0), 0.44)
|
||||||
self.assertAlmostEqual(cf.getfloat('Types', 'no-such-float',
|
self.assertAlmostEqual(cf.getfloat('Types', 'no-such-float',
|
||||||
default=0.0), 0.0)
|
fallback=0.0), 0.0)
|
||||||
eq(cf.getfloat('Types', 'no-such-float', default="0.0"), "0.0") # sic!
|
eq(cf.getfloat('Types', 'no-such-float', fallback="0.0"), "0.0") # sic!
|
||||||
eq(cf.getboolean('Types', 'boolean', default=True), False)
|
eq(cf.getboolean('Types', 'boolean', fallback=True), False)
|
||||||
eq(cf.getboolean('Types', 'no-such-boolean', default="yes"),
|
eq(cf.getboolean('Types', 'no-such-boolean', fallback="yes"),
|
||||||
"yes") # sic!
|
"yes") # sic!
|
||||||
eq(cf.getboolean('Types', 'no-such-boolean', default=True), True)
|
eq(cf.getboolean('Types', 'no-such-boolean', fallback=True), True)
|
||||||
eq(cf.getboolean('No Such Types', 'boolean', default=True), True)
|
eq(cf.getboolean('No Such Types', 'boolean', fallback=True), True)
|
||||||
if self.allow_no_value:
|
if self.allow_no_value:
|
||||||
eq(cf.get('NoValue', 'option-without-value', default=False), None)
|
eq(cf.get('NoValue', 'option-without-value', fallback=False), None)
|
||||||
eq(cf.get('NoValue', 'no-such-option-without-value',
|
eq(cf.get('NoValue', 'no-such-option-without-value',
|
||||||
default=False), False)
|
fallback=False), False)
|
||||||
|
|
||||||
|
# mapping access
|
||||||
|
eq(cf['Foo Bar']['foo'], 'bar1')
|
||||||
|
eq(cf['Spacey Bar']['foo'], 'bar2')
|
||||||
|
eq(cf['Spacey Bar From The Beginning']['foo'], 'bar3')
|
||||||
|
eq(cf['Spacey Bar From The Beginning']['baz'], 'qwe')
|
||||||
|
eq(cf['Commented Bar']['foo'], 'bar4')
|
||||||
|
eq(cf['Commented Bar']['baz'], 'qwe')
|
||||||
|
eq(cf['Spaces']['key with spaces'], 'value')
|
||||||
|
eq(cf['Spaces']['another with spaces'], 'splat!')
|
||||||
|
eq(cf['Long Line']['foo'],
|
||||||
|
'this line is much, much longer than my editor\nlikes it.')
|
||||||
|
if self.allow_no_value:
|
||||||
|
eq(cf['NoValue']['option-without-value'], None)
|
||||||
|
|
||||||
|
# API access
|
||||||
self.assertNotIn('__name__', cf.options("Foo Bar"),
|
self.assertNotIn('__name__', cf.options("Foo Bar"),
|
||||||
'__name__ "option" should not be exposed by the API!')
|
'__name__ "option" should not be exposed by the API!')
|
||||||
|
|
||||||
|
# mapping access
|
||||||
|
self.assertNotIn('__name__', cf['Foo Bar'],
|
||||||
|
'__name__ "option" should not be exposed by '
|
||||||
|
'mapping protocol access')
|
||||||
|
self.assertFalse('__name__' in cf['Foo Bar'])
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
cf['Foo Bar']['__name__']
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
del cf['Foo Bar']['__name__']
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
cf['Foo Bar']['__name__'] = "can't write to this special name"
|
||||||
|
|
||||||
# Make sure the right things happen for remove_option();
|
# Make sure the right things happen for remove_option();
|
||||||
# added to include check for SourceForge bug #123324:
|
# added to include check for SourceForge bug #123324:
|
||||||
|
|
||||||
|
# API acceess
|
||||||
self.assertTrue(cf.remove_option('Foo Bar', 'foo'),
|
self.assertTrue(cf.remove_option('Foo Bar', 'foo'),
|
||||||
"remove_option() failed to report existence of option")
|
"remove_option() failed to report existence of option")
|
||||||
self.assertFalse(cf.has_option('Foo Bar', 'foo'),
|
self.assertFalse(cf.has_option('Foo Bar', 'foo'),
|
||||||
|
@ -138,17 +179,25 @@ class BasicTestCase(CfgParserTestCaseClass):
|
||||||
eq(cf.get('Long Line', 'foo'),
|
eq(cf.get('Long Line', 'foo'),
|
||||||
'this line is much, much longer than my editor\nlikes it.')
|
'this line is much, much longer than my editor\nlikes it.')
|
||||||
|
|
||||||
|
# mapping access
|
||||||
|
del cf['Spacey Bar']['foo']
|
||||||
|
self.assertFalse('foo' in cf['Spacey Bar'])
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
del cf['Spacey Bar']['foo']
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
del cf['No Such Section']['foo']
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
config_string = """\
|
config_string = """\
|
||||||
[Foo Bar]
|
[Foo Bar]
|
||||||
foo{0[0]}bar
|
foo{0[0]}bar1
|
||||||
[Spacey Bar]
|
[Spacey Bar]
|
||||||
foo {0[0]} bar
|
foo {0[0]} bar2
|
||||||
[Spacey Bar From The Beginning]
|
[Spacey Bar From The Beginning]
|
||||||
foo {0[0]} bar
|
foo {0[0]} bar3
|
||||||
baz {0[0]} qwe
|
baz {0[0]} qwe
|
||||||
[Commented Bar]
|
[Commented Bar]
|
||||||
foo{0[1]} bar {1[1]} comment
|
foo{0[1]} bar4 {1[1]} comment
|
||||||
baz{0[0]}qwe {1[0]}another one
|
baz{0[0]}qwe {1[0]}another one
|
||||||
[Long Line]
|
[Long Line]
|
||||||
foo{0[1]} this line is much, much longer than my editor
|
foo{0[1]} this line is much, much longer than my editor
|
||||||
|
@ -205,17 +254,17 @@ boolean {0[0]} NO
|
||||||
def test_basic_from_dict(self):
|
def test_basic_from_dict(self):
|
||||||
config = {
|
config = {
|
||||||
"Foo Bar": {
|
"Foo Bar": {
|
||||||
"foo": "bar",
|
"foo": "bar1",
|
||||||
},
|
},
|
||||||
"Spacey Bar": {
|
"Spacey Bar": {
|
||||||
"foo": "bar",
|
"foo": "bar2",
|
||||||
},
|
},
|
||||||
"Spacey Bar From The Beginning": {
|
"Spacey Bar From The Beginning": {
|
||||||
"foo": "bar",
|
"foo": "bar3",
|
||||||
"baz": "qwe",
|
"baz": "qwe",
|
||||||
},
|
},
|
||||||
"Commented Bar": {
|
"Commented Bar": {
|
||||||
"foo": "bar",
|
"foo": "bar4",
|
||||||
"baz": "qwe",
|
"baz": "qwe",
|
||||||
},
|
},
|
||||||
"Long Line": {
|
"Long Line": {
|
||||||
|
@ -270,14 +319,18 @@ boolean {0[0]} NO
|
||||||
cf = self.newconfig()
|
cf = self.newconfig()
|
||||||
cf.add_section("A")
|
cf.add_section("A")
|
||||||
cf.add_section("a")
|
cf.add_section("a")
|
||||||
|
cf.add_section("B")
|
||||||
L = cf.sections()
|
L = cf.sections()
|
||||||
L.sort()
|
L.sort()
|
||||||
eq = self.assertEqual
|
eq = self.assertEqual
|
||||||
eq(L, ["A", "a"])
|
eq(L, ["A", "B", "a"])
|
||||||
cf.set("a", "B", "value")
|
cf.set("a", "B", "value")
|
||||||
eq(cf.options("a"), ["b"])
|
eq(cf.options("a"), ["b"])
|
||||||
eq(cf.get("a", "b"), "value",
|
eq(cf.get("a", "b"), "value",
|
||||||
"could not locate option, expecting case-insensitive option names")
|
"could not locate option, expecting case-insensitive option names")
|
||||||
|
with self.assertRaises(configparser.NoSectionError):
|
||||||
|
# section names are case-sensitive
|
||||||
|
cf.set("b", "A", "value")
|
||||||
self.assertTrue(cf.has_option("a", "b"))
|
self.assertTrue(cf.has_option("a", "b"))
|
||||||
cf.set("A", "A-B", "A-B value")
|
cf.set("A", "A-B", "A-B value")
|
||||||
for opt in ("a-b", "A-b", "a-B", "A-B"):
|
for opt in ("a-b", "A-b", "a-B", "A-B"):
|
||||||
|
@ -291,7 +344,7 @@ boolean {0[0]} NO
|
||||||
|
|
||||||
# SF bug #432369:
|
# SF bug #432369:
|
||||||
cf = self.fromstring(
|
cf = self.fromstring(
|
||||||
"[MySection]\nOption{} first line\n\tsecond line\n".format(
|
"[MySection]\nOption{} first line \n\tsecond line \n".format(
|
||||||
self.delimiters[0]))
|
self.delimiters[0]))
|
||||||
eq(cf.options("MySection"), ["option"])
|
eq(cf.options("MySection"), ["option"])
|
||||||
eq(cf.get("MySection", "Option"), "first line\nsecond line")
|
eq(cf.get("MySection", "Option"), "first line\nsecond line")
|
||||||
|
@ -303,6 +356,46 @@ boolean {0[0]} NO
|
||||||
self.assertTrue(cf.has_option("section", "Key"))
|
self.assertTrue(cf.has_option("section", "Key"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_case_sensitivity_mapping_access(self):
|
||||||
|
cf = self.newconfig()
|
||||||
|
cf["A"] = {}
|
||||||
|
cf["a"] = {"B": "value"}
|
||||||
|
cf["B"] = {}
|
||||||
|
L = [section for section in cf]
|
||||||
|
L.sort()
|
||||||
|
eq = self.assertEqual
|
||||||
|
elem_eq = self.assertItemsEqual
|
||||||
|
eq(L, ["A", "B", configparser.DEFAULTSECT, "a"])
|
||||||
|
eq(cf["a"].keys(), {"b"})
|
||||||
|
eq(cf["a"]["b"], "value",
|
||||||
|
"could not locate option, expecting case-insensitive option names")
|
||||||
|
with self.assertRaises(KeyError):
|
||||||
|
# section names are case-sensitive
|
||||||
|
cf["b"]["A"] = "value"
|
||||||
|
self.assertTrue("b" in cf["a"])
|
||||||
|
cf["A"]["A-B"] = "A-B value"
|
||||||
|
for opt in ("a-b", "A-b", "a-B", "A-B"):
|
||||||
|
self.assertTrue(
|
||||||
|
opt in cf["A"],
|
||||||
|
"has_option() returned false for option which should exist")
|
||||||
|
eq(cf["A"].keys(), {"a-b"})
|
||||||
|
eq(cf["a"].keys(), {"b"})
|
||||||
|
del cf["a"]["B"]
|
||||||
|
elem_eq(cf["a"].keys(), {})
|
||||||
|
|
||||||
|
# SF bug #432369:
|
||||||
|
cf = self.fromstring(
|
||||||
|
"[MySection]\nOption{} first line \n\tsecond line \n".format(
|
||||||
|
self.delimiters[0]))
|
||||||
|
eq(cf["MySection"].keys(), {"option"})
|
||||||
|
eq(cf["MySection"]["Option"], "first line\nsecond line")
|
||||||
|
|
||||||
|
# SF bug #561822:
|
||||||
|
cf = self.fromstring("[section]\n"
|
||||||
|
"nekey{}nevalue\n".format(self.delimiters[0]),
|
||||||
|
defaults={"key":"value"})
|
||||||
|
self.assertTrue("Key" in cf["section"])
|
||||||
|
|
||||||
def test_default_case_sensitivity(self):
|
def test_default_case_sensitivity(self):
|
||||||
cf = self.newconfig({"foo": "Bar"})
|
cf = self.newconfig({"foo": "Bar"})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
Loading…
Reference in New Issue