configparser: the name of the DEFAULT section is now customizable
This commit is contained in:
parent
b357fb7bed
commit
c264c098d0
|
@ -406,12 +406,13 @@ class RawConfigParser(MutableMapping):
|
||||||
def __init__(self, defaults=None, dict_type=_default_dict,
|
def __init__(self, defaults=None, dict_type=_default_dict,
|
||||||
allow_no_value=False, *, delimiters=('=', ':'),
|
allow_no_value=False, *, delimiters=('=', ':'),
|
||||||
comment_prefixes=_COMPATIBLE, strict=False,
|
comment_prefixes=_COMPATIBLE, strict=False,
|
||||||
empty_lines_in_values=True):
|
empty_lines_in_values=True,
|
||||||
|
default_section=DEFAULTSECT):
|
||||||
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._proxies = self._dict()
|
self._proxies = self._dict()
|
||||||
self._proxies[DEFAULTSECT] = SectionProxy(self, DEFAULTSECT)
|
self._proxies[default_section] = SectionProxy(self, default_section)
|
||||||
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
|
||||||
|
@ -435,6 +436,7 @@ class RawConfigParser(MutableMapping):
|
||||||
self._strict = strict
|
self._strict = strict
|
||||||
self._allow_no_value = allow_no_value
|
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
|
||||||
|
self._default_section=default_section
|
||||||
|
|
||||||
def defaults(self):
|
def defaults(self):
|
||||||
return self._defaults
|
return self._defaults
|
||||||
|
@ -448,10 +450,9 @@ class RawConfigParser(MutableMapping):
|
||||||
"""Create a new section in the configuration.
|
"""Create a new section in the configuration.
|
||||||
|
|
||||||
Raise DuplicateSectionError if a section by the specified name
|
Raise DuplicateSectionError if a section by the specified name
|
||||||
already exists. Raise ValueError if name is DEFAULT or any of it's
|
already exists. Raise ValueError if name is DEFAULT.
|
||||||
case-insensitive variants.
|
|
||||||
"""
|
"""
|
||||||
if section.upper() == DEFAULTSECT:
|
if section == self._default_section:
|
||||||
raise ValueError('Invalid section name: %s' % section)
|
raise ValueError('Invalid section name: %s' % section)
|
||||||
|
|
||||||
if section in self._sections:
|
if section in self._sections:
|
||||||
|
@ -587,7 +588,7 @@ class RawConfigParser(MutableMapping):
|
||||||
try:
|
try:
|
||||||
d2 = self._sections[section]
|
d2 = self._sections[section]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if section != DEFAULTSECT:
|
if section != self._default_section:
|
||||||
raise NoSectionError(section)
|
raise NoSectionError(section)
|
||||||
d2 = self._dict()
|
d2 = self._dict()
|
||||||
d = self._defaults.copy()
|
d = self._defaults.copy()
|
||||||
|
@ -632,7 +633,7 @@ class RawConfigParser(MutableMapping):
|
||||||
|
|
||||||
def has_option(self, section, option):
|
def has_option(self, section, option):
|
||||||
"""Check for the existence of a given option in a given section."""
|
"""Check for the existence of a given option in a given section."""
|
||||||
if not section or section == DEFAULTSECT:
|
if not section or section == self._default_section:
|
||||||
option = self.optionxform(option)
|
option = self.optionxform(option)
|
||||||
return option in self._defaults
|
return option in self._defaults
|
||||||
elif section not in self._sections:
|
elif section not in self._sections:
|
||||||
|
@ -644,7 +645,7 @@ class RawConfigParser(MutableMapping):
|
||||||
|
|
||||||
def set(self, section, option, value=None):
|
def set(self, section, option, value=None):
|
||||||
"""Set an option."""
|
"""Set an option."""
|
||||||
if not section or section == DEFAULTSECT:
|
if not section or section == self._default_section:
|
||||||
sectdict = self._defaults
|
sectdict = self._defaults
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
@ -664,7 +665,8 @@ class RawConfigParser(MutableMapping):
|
||||||
else:
|
else:
|
||||||
d = self._delimiters[0]
|
d = self._delimiters[0]
|
||||||
if self._defaults:
|
if self._defaults:
|
||||||
self._write_section(fp, DEFAULTSECT, self._defaults.items(), d)
|
self._write_section(fp, self._default_section,
|
||||||
|
self._defaults.items(), d)
|
||||||
for section in self._sections:
|
for section in self._sections:
|
||||||
self._write_section(fp, section,
|
self._write_section(fp, section,
|
||||||
self._sections[section].items(), d)
|
self._sections[section].items(), d)
|
||||||
|
@ -684,7 +686,7 @@ class RawConfigParser(MutableMapping):
|
||||||
|
|
||||||
def remove_option(self, section, option):
|
def remove_option(self, section, option):
|
||||||
"""Remove an option."""
|
"""Remove an option."""
|
||||||
if not section or section == DEFAULTSECT:
|
if not section or section == self._default_section:
|
||||||
sectdict = self._defaults
|
sectdict = self._defaults
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
@ -706,7 +708,7 @@ class RawConfigParser(MutableMapping):
|
||||||
return existed
|
return existed
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if key != DEFAULTSECT and not self.has_section(key):
|
if key != self._default_section and not self.has_section(key):
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
return self._proxies[key]
|
return self._proxies[key]
|
||||||
|
|
||||||
|
@ -720,21 +722,21 @@ class RawConfigParser(MutableMapping):
|
||||||
self.read_dict({key: value})
|
self.read_dict({key: value})
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
if key == DEFAULTSECT:
|
if key == self._default_section:
|
||||||
raise ValueError("Cannot remove the default section.")
|
raise ValueError("Cannot remove the default section.")
|
||||||
if not self.has_section(key):
|
if not self.has_section(key):
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
self.remove_section(key)
|
self.remove_section(key)
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
return key == DEFAULTSECT or self.has_section(key)
|
return key == self._default_section or self.has_section(key)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self._sections) + 1 # the default section
|
return len(self._sections) + 1 # the default section
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
# XXX does it break when underlying container state changed?
|
# XXX does it break when underlying container state changed?
|
||||||
return itertools.chain((DEFAULTSECT,), self._sections.keys())
|
return itertools.chain((self._default_section,), self._sections.keys())
|
||||||
|
|
||||||
def _read(self, fp, fpname):
|
def _read(self, fp, fpname):
|
||||||
"""Parse a sectioned configuration file.
|
"""Parse a sectioned configuration file.
|
||||||
|
@ -806,7 +808,7 @@ class RawConfigParser(MutableMapping):
|
||||||
lineno)
|
lineno)
|
||||||
cursect = self._sections[sectname]
|
cursect = self._sections[sectname]
|
||||||
elements_added.add(sectname)
|
elements_added.add(sectname)
|
||||||
elif sectname == DEFAULTSECT:
|
elif sectname == self._default_section:
|
||||||
cursect = self._defaults
|
cursect = self._defaults
|
||||||
else:
|
else:
|
||||||
cursect = self._dict()
|
cursect = self._dict()
|
||||||
|
@ -877,7 +879,7 @@ class RawConfigParser(MutableMapping):
|
||||||
try:
|
try:
|
||||||
d.update(self._sections[section])
|
d.update(self._sections[section])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if section != DEFAULTSECT:
|
if section != self._default_section:
|
||||||
raise NoSectionError(section)
|
raise NoSectionError(section)
|
||||||
# Update with the entry specific variables
|
# Update with the entry specific variables
|
||||||
if vars:
|
if vars:
|
||||||
|
@ -999,7 +1001,7 @@ class ConfigParser(RawConfigParser):
|
||||||
try:
|
try:
|
||||||
d.update(self._sections[section])
|
d.update(self._sections[section])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if section != DEFAULTSECT:
|
if section != self._default_section:
|
||||||
raise NoSectionError(section)
|
raise NoSectionError(section)
|
||||||
# Update with the entry specific variables
|
# Update with the entry specific variables
|
||||||
if vars:
|
if vars:
|
||||||
|
|
|
@ -31,6 +31,7 @@ class CfgParserTestCaseClass(unittest.TestCase):
|
||||||
empty_lines_in_values = True
|
empty_lines_in_values = True
|
||||||
dict_type = configparser._default_dict
|
dict_type = configparser._default_dict
|
||||||
strict = False
|
strict = False
|
||||||
|
default_section = configparser.DEFAULTSECT
|
||||||
|
|
||||||
def newconfig(self, defaults=None):
|
def newconfig(self, defaults=None):
|
||||||
arguments = dict(
|
arguments = dict(
|
||||||
|
@ -41,6 +42,7 @@ class CfgParserTestCaseClass(unittest.TestCase):
|
||||||
empty_lines_in_values=self.empty_lines_in_values,
|
empty_lines_in_values=self.empty_lines_in_values,
|
||||||
dict_type=self.dict_type,
|
dict_type=self.dict_type,
|
||||||
strict=self.strict,
|
strict=self.strict,
|
||||||
|
default_section=self.default_section,
|
||||||
)
|
)
|
||||||
return self.config_class(**arguments)
|
return self.config_class(**arguments)
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ class BasicTestCase(CfgParserTestCaseClass):
|
||||||
# mapping access
|
# mapping access
|
||||||
L = [section for section in cf]
|
L = [section for section in cf]
|
||||||
L.sort()
|
L.sort()
|
||||||
E.append(configparser.DEFAULTSECT)
|
E.append(self.default_section)
|
||||||
E.sort()
|
E.sort()
|
||||||
eq(L, E)
|
eq(L, E)
|
||||||
|
|
||||||
|
@ -365,7 +367,7 @@ boolean {0[0]} NO
|
||||||
L.sort()
|
L.sort()
|
||||||
eq = self.assertEqual
|
eq = self.assertEqual
|
||||||
elem_eq = self.assertItemsEqual
|
elem_eq = self.assertItemsEqual
|
||||||
eq(L, ["A", "B", configparser.DEFAULTSECT, "a"])
|
eq(L, sorted(["A", "B", self.default_section, "a"]))
|
||||||
eq(cf["a"].keys(), {"b"})
|
eq(cf["a"].keys(), {"b"})
|
||||||
eq(cf["a"]["b"], "value",
|
eq(cf["a"]["b"], "value",
|
||||||
"could not locate option, expecting case-insensitive option names")
|
"could not locate option, expecting case-insensitive option names")
|
||||||
|
@ -399,11 +401,11 @@ boolean {0[0]} NO
|
||||||
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(
|
||||||
cf.get("DEFAULT", "Foo"), "Bar",
|
cf.get(self.default_section, "Foo"), "Bar",
|
||||||
"could not locate option, expecting case-insensitive option names")
|
"could not locate option, expecting case-insensitive option names")
|
||||||
cf = self.newconfig({"Foo": "Bar"})
|
cf = self.newconfig({"Foo": "Bar"})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cf.get("DEFAULT", "Foo"), "Bar",
|
cf.get(self.default_section, "Foo"), "Bar",
|
||||||
"could not locate option, expecting case-insensitive defaults")
|
"could not locate option, expecting case-insensitive defaults")
|
||||||
|
|
||||||
def test_parse_errors(self):
|
def test_parse_errors(self):
|
||||||
|
@ -530,7 +532,7 @@ boolean {0[0]} NO
|
||||||
"[Long Line]\n"
|
"[Long Line]\n"
|
||||||
"foo{0[0]} this line is much, much longer than my editor\n"
|
"foo{0[0]} this line is much, much longer than my editor\n"
|
||||||
" likes it.\n"
|
" likes it.\n"
|
||||||
"[DEFAULT]\n"
|
"[{default_section}]\n"
|
||||||
"foo{0[1]} another very\n"
|
"foo{0[1]} another very\n"
|
||||||
" long line\n"
|
" long line\n"
|
||||||
"[Long Line - With Comments!]\n"
|
"[Long Line - With Comments!]\n"
|
||||||
|
@ -538,7 +540,8 @@ boolean {0[0]} NO
|
||||||
" also {comment} place\n"
|
" also {comment} place\n"
|
||||||
" comments {comment} in\n"
|
" comments {comment} in\n"
|
||||||
" multiline {comment} values"
|
" multiline {comment} values"
|
||||||
"\n".format(self.delimiters, comment=self.comment_prefixes[0])
|
"\n".format(self.delimiters, comment=self.comment_prefixes[0],
|
||||||
|
default_section=self.default_section)
|
||||||
)
|
)
|
||||||
if self.allow_no_value:
|
if self.allow_no_value:
|
||||||
config_string += (
|
config_string += (
|
||||||
|
@ -550,7 +553,7 @@ boolean {0[0]} NO
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
cf.write(output)
|
cf.write(output)
|
||||||
expect_string = (
|
expect_string = (
|
||||||
"[DEFAULT]\n"
|
"[{default_section}]\n"
|
||||||
"foo {equals} another very\n"
|
"foo {equals} another very\n"
|
||||||
"\tlong line\n"
|
"\tlong line\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -563,7 +566,8 @@ boolean {0[0]} NO
|
||||||
"\talso\n"
|
"\talso\n"
|
||||||
"\tcomments\n"
|
"\tcomments\n"
|
||||||
"\tmultiline\n"
|
"\tmultiline\n"
|
||||||
"\n".format(equals=self.delimiters[0])
|
"\n".format(equals=self.delimiters[0],
|
||||||
|
default_section=self.default_section)
|
||||||
)
|
)
|
||||||
if self.allow_no_value:
|
if self.allow_no_value:
|
||||||
expect_string += (
|
expect_string += (
|
||||||
|
@ -724,6 +728,9 @@ class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase):
|
||||||
delimiters = (':=', '$')
|
delimiters = (':=', '$')
|
||||||
comment_prefixes = ('//', '"')
|
comment_prefixes = ('//', '"')
|
||||||
|
|
||||||
|
class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase):
|
||||||
|
default_section = 'general'
|
||||||
|
|
||||||
class MultilineValuesTestCase(BasicTestCase):
|
class MultilineValuesTestCase(BasicTestCase):
|
||||||
config_class = configparser.ConfigParser
|
config_class = configparser.ConfigParser
|
||||||
wonderful_spam = ("I'm having spam spam spam spam "
|
wonderful_spam = ("I'm having spam spam spam spam "
|
||||||
|
@ -851,13 +858,9 @@ class SafeConfigParserTestCase(ConfigParserTestCase):
|
||||||
self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0)
|
self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0)
|
||||||
self.assertRaises(TypeError, cf.set, "sect", "option2", object())
|
self.assertRaises(TypeError, cf.set, "sect", "option2", object())
|
||||||
|
|
||||||
def test_add_section_default_1(self):
|
def test_add_section_default(self):
|
||||||
cf = self.newconfig()
|
cf = self.newconfig()
|
||||||
self.assertRaises(ValueError, cf.add_section, "default")
|
self.assertRaises(ValueError, cf.add_section, self.default_section)
|
||||||
|
|
||||||
def test_add_section_default_2(self):
|
|
||||||
cf = self.newconfig()
|
|
||||||
self.assertRaises(ValueError, cf.add_section, "DEFAULT")
|
|
||||||
|
|
||||||
class SafeConfigParserTestCaseNonStandardDelimiters(SafeConfigParserTestCase):
|
class SafeConfigParserTestCaseNonStandardDelimiters(SafeConfigParserTestCase):
|
||||||
delimiters = (':=', '$')
|
delimiters = (':=', '$')
|
||||||
|
@ -884,11 +887,12 @@ class SafeConfigParserTestCaseTrickyFile(CfgParserTestCaseClass):
|
||||||
'no values here',
|
'no values here',
|
||||||
'tricky interpolation',
|
'tricky interpolation',
|
||||||
'more interpolation'])
|
'more interpolation'])
|
||||||
self.assertEqual(cf.getint('DEFAULT', 'go',
|
self.assertEqual(cf.getint(self.default_section, 'go',
|
||||||
vars={'interpolate': '-1'}), -1)
|
vars={'interpolate': '-1'}), -1)
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
# no interpolation will happen
|
# no interpolation will happen
|
||||||
cf.getint('DEFAULT', 'go', raw=True, vars={'interpolate': '-1'})
|
cf.getint(self.default_section, 'go', raw=True,
|
||||||
|
vars={'interpolate': '-1'})
|
||||||
self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4)
|
self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4)
|
||||||
self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10)
|
self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10)
|
||||||
longname = 'yeah, sections can be indented as well'
|
longname = 'yeah, sections can be indented as well'
|
||||||
|
@ -997,6 +1001,7 @@ def test_main():
|
||||||
Issue7005TestCase,
|
Issue7005TestCase,
|
||||||
StrictTestCase,
|
StrictTestCase,
|
||||||
CompatibleTestCase,
|
CompatibleTestCase,
|
||||||
|
ConfigParserTestCaseNonStandardDefaultSection,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue