mirror of https://github.com/python/cpython
2125 lines
84 KiB
Python
2125 lines
84 KiB
Python
import collections
|
|
import configparser
|
|
import io
|
|
import os
|
|
import pathlib
|
|
import textwrap
|
|
import unittest
|
|
|
|
from test import support
|
|
from test.support import os_helper
|
|
|
|
|
|
class SortedDict(collections.UserDict):
|
|
|
|
def items(self):
|
|
return sorted(self.data.items())
|
|
|
|
def keys(self):
|
|
return sorted(self.data.keys())
|
|
|
|
def values(self):
|
|
return [i[1] for i in self.items()]
|
|
|
|
def iteritems(self):
|
|
return iter(self.items())
|
|
|
|
def iterkeys(self):
|
|
return iter(self.keys())
|
|
|
|
def itervalues(self):
|
|
return iter(self.values())
|
|
|
|
__iter__ = iterkeys
|
|
|
|
|
|
class CfgParserTestCaseClass:
|
|
allow_no_value = False
|
|
delimiters = ('=', ':')
|
|
comment_prefixes = (';', '#')
|
|
inline_comment_prefixes = (';', '#')
|
|
empty_lines_in_values = True
|
|
dict_type = configparser._default_dict
|
|
strict = False
|
|
default_section = configparser.DEFAULTSECT
|
|
interpolation = configparser._UNSET
|
|
|
|
def newconfig(self, defaults=None):
|
|
arguments = dict(
|
|
defaults=defaults,
|
|
allow_no_value=self.allow_no_value,
|
|
delimiters=self.delimiters,
|
|
comment_prefixes=self.comment_prefixes,
|
|
inline_comment_prefixes=self.inline_comment_prefixes,
|
|
empty_lines_in_values=self.empty_lines_in_values,
|
|
dict_type=self.dict_type,
|
|
strict=self.strict,
|
|
default_section=self.default_section,
|
|
interpolation=self.interpolation,
|
|
)
|
|
instance = self.config_class(**arguments)
|
|
return instance
|
|
|
|
def fromstring(self, string, defaults=None):
|
|
cf = self.newconfig(defaults)
|
|
cf.read_string(string)
|
|
return cf
|
|
|
|
|
|
class BasicTestCase(CfgParserTestCaseClass):
|
|
|
|
def basic_test(self, cf):
|
|
E = ['Commented Bar',
|
|
'Foo Bar',
|
|
'Internationalized Stuff',
|
|
'Long Line',
|
|
'Section\\with$weird%characters[\t',
|
|
'Spaces',
|
|
'Spacey Bar',
|
|
'Spacey Bar From The Beginning',
|
|
'Types',
|
|
'This One Has A ] In It',
|
|
]
|
|
|
|
if self.allow_no_value:
|
|
E.append('NoValue')
|
|
E.sort()
|
|
F = [('baz', 'qwe'), ('foo', 'bar3')]
|
|
|
|
# API access
|
|
L = cf.sections()
|
|
L.sort()
|
|
eq = self.assertEqual
|
|
eq(L, E)
|
|
L = cf.items('Spacey Bar From The Beginning')
|
|
L.sort()
|
|
eq(L, F)
|
|
|
|
# mapping access
|
|
L = [section for section in cf]
|
|
L.sort()
|
|
E.append(self.default_section)
|
|
E.sort()
|
|
eq(L, E)
|
|
L = cf['Spacey Bar From The Beginning'].items()
|
|
L = sorted(list(L))
|
|
eq(L, F)
|
|
L = cf.items()
|
|
L = sorted(list(L))
|
|
self.assertEqual(len(L), len(E))
|
|
for name, section in L:
|
|
eq(name, section.name)
|
|
eq(cf.defaults(), cf[self.default_section])
|
|
|
|
# The use of spaces in the section names serves as a
|
|
# regression test for SourceForge bug #583248:
|
|
# https://bugs.python.org/issue583248
|
|
|
|
# API access
|
|
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('Commented Bar', 'foo'), 'bar4')
|
|
eq(cf.get('Commented Bar', 'baz'), 'qwe')
|
|
eq(cf.get('Spaces', 'key with spaces'), 'value')
|
|
eq(cf.get('Spaces', 'another with spaces'), 'splat!')
|
|
eq(cf.getint('Types', 'int'), 42)
|
|
eq(cf.get('Types', 'int'), "42")
|
|
self.assertAlmostEqual(cf.getfloat('Types', 'float'), 0.44)
|
|
eq(cf.get('Types', 'float'), "0.44")
|
|
eq(cf.getboolean('Types', 'boolean'), False)
|
|
eq(cf.get('Types', '123'), 'strange but acceptable')
|
|
eq(cf.get('This One Has A ] In It', 'forks'), 'spoons')
|
|
if self.allow_no_value:
|
|
eq(cf.get('NoValue', 'option-without-value'), None)
|
|
|
|
# test vars= and fallback=
|
|
eq(cf.get('Foo Bar', 'foo', fallback='baz'), 'bar1')
|
|
eq(cf.get('Foo Bar', 'foo', vars={'foo': 'baz'}), 'baz')
|
|
with self.assertRaises(configparser.NoSectionError):
|
|
cf.get('No Such Foo Bar', 'foo')
|
|
with self.assertRaises(configparser.NoOptionError):
|
|
cf.get('Foo Bar', 'no-such-foo')
|
|
eq(cf.get('No Such Foo Bar', 'foo', fallback='baz'), 'baz')
|
|
eq(cf.get('Foo Bar', 'no-such-foo', fallback='baz'), 'baz')
|
|
eq(cf.get('Spacey Bar', 'foo', fallback=None), 'bar2')
|
|
eq(cf.get('No Such Spacey Bar', 'foo', fallback=None), None)
|
|
eq(cf.getint('Types', 'int', fallback=18), 42)
|
|
eq(cf.getint('Types', 'no-such-int', fallback=18), 18)
|
|
eq(cf.getint('Types', 'no-such-int', fallback="18"), "18") # sic!
|
|
with self.assertRaises(configparser.NoOptionError):
|
|
cf.getint('Types', 'no-such-int')
|
|
self.assertAlmostEqual(cf.getfloat('Types', 'float',
|
|
fallback=0.0), 0.44)
|
|
self.assertAlmostEqual(cf.getfloat('Types', 'no-such-float',
|
|
fallback=0.0), 0.0)
|
|
eq(cf.getfloat('Types', 'no-such-float', fallback="0.0"), "0.0") # sic!
|
|
with self.assertRaises(configparser.NoOptionError):
|
|
cf.getfloat('Types', 'no-such-float')
|
|
eq(cf.getboolean('Types', 'boolean', fallback=True), False)
|
|
eq(cf.getboolean('Types', 'no-such-boolean', fallback="yes"),
|
|
"yes") # sic!
|
|
eq(cf.getboolean('Types', 'no-such-boolean', fallback=True), True)
|
|
with self.assertRaises(configparser.NoOptionError):
|
|
cf.getboolean('Types', 'no-such-boolean')
|
|
eq(cf.getboolean('No Such Types', 'boolean', fallback=True), True)
|
|
if self.allow_no_value:
|
|
eq(cf.get('NoValue', 'option-without-value', fallback=False), None)
|
|
eq(cf.get('NoValue', 'no-such-option-without-value',
|
|
fallback=False), False)
|
|
|
|
# mapping access
|
|
eq(cf['Foo Bar']['foo'], 'bar1')
|
|
eq(cf['Spacey Bar']['foo'], 'bar2')
|
|
section = cf['Spacey Bar From The Beginning']
|
|
eq(section.name, 'Spacey Bar From The Beginning')
|
|
self.assertIs(section.parser, cf)
|
|
with self.assertRaises(AttributeError):
|
|
section.name = 'Name is read-only'
|
|
with self.assertRaises(AttributeError):
|
|
section.parser = 'Parser is read-only'
|
|
eq(section['foo'], 'bar3')
|
|
eq(section['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)
|
|
# test vars= and fallback=
|
|
eq(cf['Foo Bar'].get('foo', 'baz'), 'bar1')
|
|
eq(cf['Foo Bar'].get('foo', fallback='baz'), 'bar1')
|
|
eq(cf['Foo Bar'].get('foo', vars={'foo': 'baz'}), 'baz')
|
|
with self.assertRaises(KeyError):
|
|
cf['No Such Foo Bar']['foo']
|
|
with self.assertRaises(KeyError):
|
|
cf['Foo Bar']['no-such-foo']
|
|
with self.assertRaises(KeyError):
|
|
cf['No Such Foo Bar'].get('foo', fallback='baz')
|
|
eq(cf['Foo Bar'].get('no-such-foo', 'baz'), 'baz')
|
|
eq(cf['Foo Bar'].get('no-such-foo', fallback='baz'), 'baz')
|
|
eq(cf['Foo Bar'].get('no-such-foo'), None)
|
|
eq(cf['Spacey Bar'].get('foo', None), 'bar2')
|
|
eq(cf['Spacey Bar'].get('foo', fallback=None), 'bar2')
|
|
with self.assertRaises(KeyError):
|
|
cf['No Such Spacey Bar'].get('foo', None)
|
|
eq(cf['Types'].getint('int', 18), 42)
|
|
eq(cf['Types'].getint('int', fallback=18), 42)
|
|
eq(cf['Types'].getint('no-such-int', 18), 18)
|
|
eq(cf['Types'].getint('no-such-int', fallback=18), 18)
|
|
eq(cf['Types'].getint('no-such-int', "18"), "18") # sic!
|
|
eq(cf['Types'].getint('no-such-int', fallback="18"), "18") # sic!
|
|
eq(cf['Types'].getint('no-such-int'), None)
|
|
self.assertAlmostEqual(cf['Types'].getfloat('float', 0.0), 0.44)
|
|
self.assertAlmostEqual(cf['Types'].getfloat('float',
|
|
fallback=0.0), 0.44)
|
|
self.assertAlmostEqual(cf['Types'].getfloat('no-such-float', 0.0), 0.0)
|
|
self.assertAlmostEqual(cf['Types'].getfloat('no-such-float',
|
|
fallback=0.0), 0.0)
|
|
eq(cf['Types'].getfloat('no-such-float', "0.0"), "0.0") # sic!
|
|
eq(cf['Types'].getfloat('no-such-float', fallback="0.0"), "0.0") # sic!
|
|
eq(cf['Types'].getfloat('no-such-float'), None)
|
|
eq(cf['Types'].getboolean('boolean', True), False)
|
|
eq(cf['Types'].getboolean('boolean', fallback=True), False)
|
|
eq(cf['Types'].getboolean('no-such-boolean', "yes"), "yes") # sic!
|
|
eq(cf['Types'].getboolean('no-such-boolean', fallback="yes"),
|
|
"yes") # sic!
|
|
eq(cf['Types'].getboolean('no-such-boolean', True), True)
|
|
eq(cf['Types'].getboolean('no-such-boolean', fallback=True), True)
|
|
eq(cf['Types'].getboolean('no-such-boolean'), None)
|
|
if self.allow_no_value:
|
|
eq(cf['NoValue'].get('option-without-value', False), None)
|
|
eq(cf['NoValue'].get('option-without-value', fallback=False), None)
|
|
eq(cf['NoValue'].get('no-such-option-without-value', False), False)
|
|
eq(cf['NoValue'].get('no-such-option-without-value',
|
|
fallback=False), False)
|
|
|
|
# Make sure the right things happen for remove_section() and
|
|
# remove_option(); added to include check for SourceForge bug #123324.
|
|
|
|
cf[self.default_section]['this_value'] = '1'
|
|
cf[self.default_section]['that_value'] = '2'
|
|
|
|
# API access
|
|
self.assertTrue(cf.remove_section('Spaces'))
|
|
self.assertFalse(cf.has_option('Spaces', 'key with spaces'))
|
|
self.assertFalse(cf.remove_section('Spaces'))
|
|
self.assertFalse(cf.remove_section(self.default_section))
|
|
self.assertTrue(cf.remove_option('Foo Bar', 'foo'),
|
|
"remove_option() failed to report existence of option")
|
|
self.assertFalse(cf.has_option('Foo Bar', 'foo'),
|
|
"remove_option() failed to remove option")
|
|
self.assertFalse(cf.remove_option('Foo Bar', 'foo'),
|
|
"remove_option() failed to report non-existence of option"
|
|
" that was removed")
|
|
self.assertTrue(cf.has_option('Foo Bar', 'this_value'))
|
|
self.assertFalse(cf.remove_option('Foo Bar', 'this_value'))
|
|
self.assertTrue(cf.remove_option(self.default_section, 'this_value'))
|
|
self.assertFalse(cf.has_option('Foo Bar', 'this_value'))
|
|
self.assertFalse(cf.remove_option(self.default_section, 'this_value'))
|
|
|
|
with self.assertRaises(configparser.NoSectionError) as cm:
|
|
cf.remove_option('No Such Section', 'foo')
|
|
self.assertEqual(cm.exception.args, ('No Such Section',))
|
|
|
|
eq(cf.get('Long Line', 'foo'),
|
|
'this line is much, much longer than my editor\nlikes it.')
|
|
|
|
# mapping access
|
|
del cf['Types']
|
|
self.assertFalse('Types' in cf)
|
|
with self.assertRaises(KeyError):
|
|
del cf['Types']
|
|
with self.assertRaises(ValueError):
|
|
del cf[self.default_section]
|
|
del cf['Spacey Bar']['foo']
|
|
self.assertFalse('foo' in cf['Spacey Bar'])
|
|
with self.assertRaises(KeyError):
|
|
del cf['Spacey Bar']['foo']
|
|
self.assertTrue('that_value' in cf['Spacey Bar'])
|
|
with self.assertRaises(KeyError):
|
|
del cf['Spacey Bar']['that_value']
|
|
del cf[self.default_section]['that_value']
|
|
self.assertFalse('that_value' in cf['Spacey Bar'])
|
|
with self.assertRaises(KeyError):
|
|
del cf[self.default_section]['that_value']
|
|
with self.assertRaises(KeyError):
|
|
del cf['No Such Section']['foo']
|
|
|
|
# Don't add new asserts below in this method as most of the options
|
|
# and sections are now removed.
|
|
|
|
def test_basic(self):
|
|
config_string = """\
|
|
[Foo Bar]
|
|
foo{0[0]}bar1
|
|
[Spacey Bar]
|
|
foo {0[0]} bar2
|
|
[Spacey Bar From The Beginning]
|
|
foo {0[0]} bar3
|
|
baz {0[0]} qwe
|
|
[Commented Bar]
|
|
foo{0[1]} bar4 {1[1]} comment
|
|
baz{0[0]}qwe {1[0]}another one
|
|
[Long Line]
|
|
foo{0[1]} this line is much, much longer than my editor
|
|
likes it.
|
|
[Section\\with$weird%characters[\t]
|
|
[Internationalized Stuff]
|
|
foo[bg]{0[1]} Bulgarian
|
|
foo{0[0]}Default
|
|
foo[en]{0[0]}English
|
|
foo[de]{0[0]}Deutsch
|
|
[Spaces]
|
|
key with spaces {0[1]} value
|
|
another with spaces {0[0]} splat!
|
|
[Types]
|
|
int {0[1]} 42
|
|
float {0[0]} 0.44
|
|
boolean {0[0]} NO
|
|
123 {0[1]} strange but acceptable
|
|
[This One Has A ] In It]
|
|
forks {0[0]} spoons
|
|
""".format(self.delimiters, self.comment_prefixes)
|
|
if self.allow_no_value:
|
|
config_string += (
|
|
"[NoValue]\n"
|
|
"option-without-value\n"
|
|
)
|
|
cf = self.fromstring(config_string)
|
|
self.basic_test(cf)
|
|
if self.strict:
|
|
with self.assertRaises(configparser.DuplicateOptionError):
|
|
cf.read_string(textwrap.dedent("""\
|
|
[Duplicate Options Here]
|
|
option {0[0]} with a value
|
|
option {0[1]} with another value
|
|
""".format(self.delimiters)))
|
|
with self.assertRaises(configparser.DuplicateSectionError):
|
|
cf.read_string(textwrap.dedent("""\
|
|
[And Now For Something]
|
|
completely different {0[0]} True
|
|
[And Now For Something]
|
|
the larch {0[1]} 1
|
|
""".format(self.delimiters)))
|
|
else:
|
|
cf.read_string(textwrap.dedent("""\
|
|
[Duplicate Options Here]
|
|
option {0[0]} with a value
|
|
option {0[1]} with another value
|
|
""".format(self.delimiters)))
|
|
|
|
cf.read_string(textwrap.dedent("""\
|
|
[And Now For Something]
|
|
completely different {0[0]} True
|
|
[And Now For Something]
|
|
the larch {0[1]} 1
|
|
""".format(self.delimiters)))
|
|
|
|
def test_basic_from_dict(self):
|
|
config = {
|
|
"Foo Bar": {
|
|
"foo": "bar1",
|
|
},
|
|
"Spacey Bar": {
|
|
"foo": "bar2",
|
|
},
|
|
"Spacey Bar From The Beginning": {
|
|
"foo": "bar3",
|
|
"baz": "qwe",
|
|
},
|
|
"Commented Bar": {
|
|
"foo": "bar4",
|
|
"baz": "qwe",
|
|
},
|
|
"Long Line": {
|
|
"foo": "this line is much, much longer than my editor\nlikes "
|
|
"it.",
|
|
},
|
|
"Section\\with$weird%characters[\t": {
|
|
},
|
|
"Internationalized Stuff": {
|
|
"foo[bg]": "Bulgarian",
|
|
"foo": "Default",
|
|
"foo[en]": "English",
|
|
"foo[de]": "Deutsch",
|
|
},
|
|
"Spaces": {
|
|
"key with spaces": "value",
|
|
"another with spaces": "splat!",
|
|
},
|
|
"Types": {
|
|
"int": 42,
|
|
"float": 0.44,
|
|
"boolean": False,
|
|
123: "strange but acceptable",
|
|
},
|
|
"This One Has A ] In It": {
|
|
"forks": "spoons"
|
|
},
|
|
}
|
|
if self.allow_no_value:
|
|
config.update({
|
|
"NoValue": {
|
|
"option-without-value": None,
|
|
}
|
|
})
|
|
cf = self.newconfig()
|
|
cf.read_dict(config)
|
|
self.basic_test(cf)
|
|
if self.strict:
|
|
with self.assertRaises(configparser.DuplicateSectionError):
|
|
cf.read_dict({
|
|
'1': {'key': 'value'},
|
|
1: {'key2': 'value2'},
|
|
})
|
|
with self.assertRaises(configparser.DuplicateOptionError):
|
|
cf.read_dict({
|
|
"Duplicate Options Here": {
|
|
'option': 'with a value',
|
|
'OPTION': 'with another value',
|
|
},
|
|
})
|
|
else:
|
|
cf.read_dict({
|
|
'section': {'key': 'value'},
|
|
'SECTION': {'key2': 'value2'},
|
|
})
|
|
cf.read_dict({
|
|
"Duplicate Options Here": {
|
|
'option': 'with a value',
|
|
'OPTION': 'with another value',
|
|
},
|
|
})
|
|
|
|
def test_case_sensitivity(self):
|
|
cf = self.newconfig()
|
|
cf.add_section("A")
|
|
cf.add_section("a")
|
|
cf.add_section("B")
|
|
L = cf.sections()
|
|
L.sort()
|
|
eq = self.assertEqual
|
|
eq(L, ["A", "B", "a"])
|
|
cf.set("a", "B", "value")
|
|
eq(cf.options("a"), ["b"])
|
|
eq(cf.get("a", "b"), "value",
|
|
"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.assertFalse(cf.has_option("b", "b"))
|
|
cf.set("A", "A-B", "A-B value")
|
|
for opt in ("a-b", "A-b", "a-B", "A-B"):
|
|
self.assertTrue(
|
|
cf.has_option("A", opt),
|
|
"has_option() returned false for option which should exist")
|
|
eq(cf.options("A"), ["a-b"])
|
|
eq(cf.options("a"), ["b"])
|
|
cf.remove_option("a", "B")
|
|
eq(cf.options("a"), [])
|
|
|
|
# SF bug #432369:
|
|
cf = self.fromstring(
|
|
"[MySection]\nOption{} first line \n\tsecond line \n".format(
|
|
self.delimiters[0]))
|
|
eq(cf.options("MySection"), ["option"])
|
|
eq(cf.get("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(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.assertCountEqual
|
|
eq(L, sorted(["A", "B", self.default_section, "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):
|
|
cf = self.newconfig({"foo": "Bar"})
|
|
self.assertEqual(
|
|
cf.get(self.default_section, "Foo"), "Bar",
|
|
"could not locate option, expecting case-insensitive option names")
|
|
cf = self.newconfig({"Foo": "Bar"})
|
|
self.assertEqual(
|
|
cf.get(self.default_section, "Foo"), "Bar",
|
|
"could not locate option, expecting case-insensitive defaults")
|
|
|
|
def test_parse_errors(self):
|
|
cf = self.newconfig()
|
|
self.parse_error(cf, configparser.ParsingError,
|
|
"[Foo]\n"
|
|
"{}val-without-opt-name\n".format(self.delimiters[0]))
|
|
self.parse_error(cf, configparser.ParsingError,
|
|
"[Foo]\n"
|
|
"{}val-without-opt-name\n".format(self.delimiters[1]))
|
|
e = self.parse_error(cf, configparser.MissingSectionHeaderError,
|
|
"No Section!\n")
|
|
self.assertEqual(e.args, ('<???>', 1, "No Section!\n"))
|
|
if not self.allow_no_value:
|
|
e = self.parse_error(cf, configparser.ParsingError,
|
|
"[Foo]\n wrong-indent\n")
|
|
self.assertEqual(e.args, ('<???>',))
|
|
# read_file on a real file
|
|
tricky = support.findfile("cfgparser.3", subdir="configdata")
|
|
if self.delimiters[0] == '=':
|
|
error = configparser.ParsingError
|
|
expected = (tricky,)
|
|
else:
|
|
error = configparser.MissingSectionHeaderError
|
|
expected = (tricky, 1,
|
|
' # INI with as many tricky parts as possible\n')
|
|
with open(tricky, encoding='utf-8') as f:
|
|
e = self.parse_error(cf, error, f)
|
|
self.assertEqual(e.args, expected)
|
|
|
|
def parse_error(self, cf, exc, src):
|
|
if hasattr(src, 'readline'):
|
|
sio = src
|
|
else:
|
|
sio = io.StringIO(src)
|
|
with self.assertRaises(exc) as cm:
|
|
cf.read_file(sio)
|
|
return cm.exception
|
|
|
|
def test_query_errors(self):
|
|
cf = self.newconfig()
|
|
self.assertEqual(cf.sections(), [],
|
|
"new ConfigParser should have no defined sections")
|
|
self.assertFalse(cf.has_section("Foo"),
|
|
"new ConfigParser should have no acknowledged "
|
|
"sections")
|
|
with self.assertRaises(configparser.NoSectionError):
|
|
cf.options("Foo")
|
|
with self.assertRaises(configparser.NoSectionError):
|
|
cf.set("foo", "bar", "value")
|
|
e = self.get_error(cf, configparser.NoSectionError, "foo", "bar")
|
|
self.assertEqual(e.args, ("foo",))
|
|
cf.add_section("foo")
|
|
e = self.get_error(cf, configparser.NoOptionError, "foo", "bar")
|
|
self.assertEqual(e.args, ("bar", "foo"))
|
|
|
|
def get_error(self, cf, exc, section, option):
|
|
try:
|
|
cf.get(section, option)
|
|
except exc as e:
|
|
return e
|
|
else:
|
|
self.fail("expected exception type %s.%s"
|
|
% (exc.__module__, exc.__qualname__))
|
|
|
|
def test_boolean(self):
|
|
cf = self.fromstring(
|
|
"[BOOLTEST]\n"
|
|
"T1{equals}1\n"
|
|
"T2{equals}TRUE\n"
|
|
"T3{equals}True\n"
|
|
"T4{equals}oN\n"
|
|
"T5{equals}yes\n"
|
|
"F1{equals}0\n"
|
|
"F2{equals}FALSE\n"
|
|
"F3{equals}False\n"
|
|
"F4{equals}oFF\n"
|
|
"F5{equals}nO\n"
|
|
"E1{equals}2\n"
|
|
"E2{equals}foo\n"
|
|
"E3{equals}-1\n"
|
|
"E4{equals}0.1\n"
|
|
"E5{equals}FALSE AND MORE".format(equals=self.delimiters[0])
|
|
)
|
|
for x in range(1, 5):
|
|
self.assertTrue(cf.getboolean('BOOLTEST', 't%d' % x))
|
|
self.assertFalse(cf.getboolean('BOOLTEST', 'f%d' % x))
|
|
self.assertRaises(ValueError,
|
|
cf.getboolean, 'BOOLTEST', 'e%d' % x)
|
|
|
|
def test_weird_errors(self):
|
|
cf = self.newconfig()
|
|
cf.add_section("Foo")
|
|
with self.assertRaises(configparser.DuplicateSectionError) as cm:
|
|
cf.add_section("Foo")
|
|
e = cm.exception
|
|
self.assertEqual(str(e), "Section 'Foo' already exists")
|
|
self.assertEqual(e.args, ("Foo", None, None))
|
|
|
|
if self.strict:
|
|
with self.assertRaises(configparser.DuplicateSectionError) as cm:
|
|
cf.read_string(textwrap.dedent("""\
|
|
[Foo]
|
|
will this be added{equals}True
|
|
[Bar]
|
|
what about this{equals}True
|
|
[Foo]
|
|
oops{equals}this won't
|
|
""".format(equals=self.delimiters[0])), source='<foo-bar>')
|
|
e = cm.exception
|
|
self.assertEqual(str(e), "While reading from '<foo-bar>' "
|
|
"[line 5]: section 'Foo' already exists")
|
|
self.assertEqual(e.args, ("Foo", '<foo-bar>', 5))
|
|
|
|
with self.assertRaises(configparser.DuplicateOptionError) as cm:
|
|
cf.read_dict({'Bar': {'opt': 'val', 'OPT': 'is really `opt`'}})
|
|
e = cm.exception
|
|
self.assertEqual(str(e), "While reading from '<dict>': option "
|
|
"'opt' in section 'Bar' already exists")
|
|
self.assertEqual(e.args, ("Bar", "opt", "<dict>", None))
|
|
|
|
def test_get_after_duplicate_option_error(self):
|
|
cf = self.newconfig()
|
|
ini = textwrap.dedent("""\
|
|
[Foo]
|
|
x{equals}1
|
|
y{equals}2
|
|
y{equals}3
|
|
""".format(equals=self.delimiters[0]))
|
|
if self.strict:
|
|
with self.assertRaises(configparser.DuplicateOptionError):
|
|
cf.read_string(ini)
|
|
else:
|
|
cf.read_string(ini)
|
|
self.assertEqual(cf.get('Foo', 'x'), '1')
|
|
|
|
def test_write(self):
|
|
config_string = (
|
|
"[Long Line]\n"
|
|
"foo{0[0]} this line is much, much longer than my editor\n"
|
|
" likes it.\n"
|
|
"[{default_section}]\n"
|
|
"foo{0[1]} another very\n"
|
|
" long line\n"
|
|
"[Long Line - With Comments!]\n"
|
|
"test {0[1]} we {comment} can\n"
|
|
" also {comment} place\n"
|
|
" comments {comment} in\n"
|
|
" multiline {comment} values"
|
|
"\n".format(self.delimiters, comment=self.comment_prefixes[0],
|
|
default_section=self.default_section)
|
|
)
|
|
if self.allow_no_value:
|
|
config_string += (
|
|
"[Valueless]\n"
|
|
"option-without-value\n"
|
|
)
|
|
|
|
cf = self.fromstring(config_string)
|
|
for space_around_delimiters in (True, False):
|
|
output = io.StringIO()
|
|
cf.write(output, space_around_delimiters=space_around_delimiters)
|
|
delimiter = self.delimiters[0]
|
|
if space_around_delimiters:
|
|
delimiter = " {} ".format(delimiter)
|
|
expect_string = (
|
|
"[{default_section}]\n"
|
|
"foo{equals}another very\n"
|
|
"\tlong line\n"
|
|
"\n"
|
|
"[Long Line]\n"
|
|
"foo{equals}this line is much, much longer than my editor\n"
|
|
"\tlikes it.\n"
|
|
"\n"
|
|
"[Long Line - With Comments!]\n"
|
|
"test{equals}we\n"
|
|
"\talso\n"
|
|
"\tcomments\n"
|
|
"\tmultiline\n"
|
|
"\n".format(equals=delimiter,
|
|
default_section=self.default_section)
|
|
)
|
|
if self.allow_no_value:
|
|
expect_string += (
|
|
"[Valueless]\n"
|
|
"option-without-value\n"
|
|
"\n"
|
|
)
|
|
self.assertEqual(output.getvalue(), expect_string)
|
|
|
|
def test_set_string_types(self):
|
|
cf = self.fromstring("[sect]\n"
|
|
"option1{eq}foo\n".format(eq=self.delimiters[0]))
|
|
# Check that we don't get an exception when setting values in
|
|
# an existing section using strings:
|
|
class mystr(str):
|
|
pass
|
|
cf.set("sect", "option1", "splat")
|
|
cf.set("sect", "option1", mystr("splat"))
|
|
cf.set("sect", "option2", "splat")
|
|
cf.set("sect", "option2", mystr("splat"))
|
|
cf.set("sect", "option1", "splat")
|
|
cf.set("sect", "option2", "splat")
|
|
|
|
def test_read_returns_file_list(self):
|
|
if self.delimiters[0] != '=':
|
|
self.skipTest('incompatible format')
|
|
file1 = support.findfile("cfgparser.1", subdir="configdata")
|
|
# check when we pass a mix of readable and non-readable files:
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read([file1, "nonexistent-file"], encoding="utf-8")
|
|
self.assertEqual(parsed_files, [file1])
|
|
self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
|
|
# check when we pass only a filename:
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read(file1, encoding="utf-8")
|
|
self.assertEqual(parsed_files, [file1])
|
|
self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
|
|
# check when we pass only a Path object:
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read(pathlib.Path(file1), encoding="utf-8")
|
|
self.assertEqual(parsed_files, [file1])
|
|
self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
|
|
# check when we passed both a filename and a Path object:
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read([pathlib.Path(file1), file1], encoding="utf-8")
|
|
self.assertEqual(parsed_files, [file1, file1])
|
|
self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
|
|
# check when we pass only missing files:
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read(["nonexistent-file"], encoding="utf-8")
|
|
self.assertEqual(parsed_files, [])
|
|
# check when we pass no files:
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read([], encoding="utf-8")
|
|
self.assertEqual(parsed_files, [])
|
|
|
|
def test_read_returns_file_list_with_bytestring_path(self):
|
|
if self.delimiters[0] != '=':
|
|
self.skipTest('incompatible format')
|
|
file1_bytestring = support.findfile("cfgparser.1", subdir="configdata").encode()
|
|
# check when passing an existing bytestring path
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read(file1_bytestring, encoding="utf-8")
|
|
self.assertEqual(parsed_files, [file1_bytestring])
|
|
# check when passing an non-existing bytestring path
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read(b'nonexistent-file', encoding="utf-8")
|
|
self.assertEqual(parsed_files, [])
|
|
# check when passing both an existing and non-existing bytestring path
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read([file1_bytestring, b'nonexistent-file'], encoding="utf-8")
|
|
self.assertEqual(parsed_files, [file1_bytestring])
|
|
|
|
# shared by subclasses
|
|
def get_interpolation_config(self):
|
|
return self.fromstring(
|
|
"[Foo]\n"
|
|
"bar{equals}something %(with1)s interpolation (1 step)\n"
|
|
"bar9{equals}something %(with9)s lots of interpolation (9 steps)\n"
|
|
"bar10{equals}something %(with10)s lots of interpolation (10 steps)\n"
|
|
"bar11{equals}something %(with11)s lots of interpolation (11 steps)\n"
|
|
"with11{equals}%(with10)s\n"
|
|
"with10{equals}%(with9)s\n"
|
|
"with9{equals}%(with8)s\n"
|
|
"with8{equals}%(With7)s\n"
|
|
"with7{equals}%(WITH6)s\n"
|
|
"with6{equals}%(with5)s\n"
|
|
"With5{equals}%(with4)s\n"
|
|
"WITH4{equals}%(with3)s\n"
|
|
"with3{equals}%(with2)s\n"
|
|
"with2{equals}%(with1)s\n"
|
|
"with1{equals}with\n"
|
|
"\n"
|
|
"[Mutual Recursion]\n"
|
|
"foo{equals}%(bar)s\n"
|
|
"bar{equals}%(foo)s\n"
|
|
"\n"
|
|
"[Interpolation Error]\n"
|
|
# no definition for 'reference'
|
|
"name{equals}%(reference)s\n".format(equals=self.delimiters[0]))
|
|
|
|
def check_items_config(self, expected):
|
|
cf = self.fromstring("""
|
|
[section]
|
|
name {0[0]} %(value)s
|
|
key{0[1]} |%(name)s|
|
|
getdefault{0[1]} |%(default)s|
|
|
""".format(self.delimiters), defaults={"default": "<default>"})
|
|
L = list(cf.items("section", vars={'value': 'value'}))
|
|
L.sort()
|
|
self.assertEqual(L, expected)
|
|
with self.assertRaises(configparser.NoSectionError):
|
|
cf.items("no such section")
|
|
|
|
def test_popitem(self):
|
|
cf = self.fromstring("""
|
|
[section1]
|
|
name1 {0[0]} value1
|
|
[section2]
|
|
name2 {0[0]} value2
|
|
[section3]
|
|
name3 {0[0]} value3
|
|
""".format(self.delimiters), defaults={"default": "<default>"})
|
|
self.assertEqual(cf.popitem()[0], 'section1')
|
|
self.assertEqual(cf.popitem()[0], 'section2')
|
|
self.assertEqual(cf.popitem()[0], 'section3')
|
|
with self.assertRaises(KeyError):
|
|
cf.popitem()
|
|
|
|
def test_clear(self):
|
|
cf = self.newconfig({"foo": "Bar"})
|
|
self.assertEqual(
|
|
cf.get(self.default_section, "Foo"), "Bar",
|
|
"could not locate option, expecting case-insensitive option names")
|
|
cf['zing'] = {'option1': 'value1', 'option2': 'value2'}
|
|
self.assertEqual(cf.sections(), ['zing'])
|
|
self.assertEqual(set(cf['zing'].keys()), {'option1', 'option2', 'foo'})
|
|
cf.clear()
|
|
self.assertEqual(set(cf.sections()), set())
|
|
self.assertEqual(set(cf[self.default_section].keys()), {'foo'})
|
|
|
|
def test_setitem(self):
|
|
cf = self.fromstring("""
|
|
[section1]
|
|
name1 {0[0]} value1
|
|
[section2]
|
|
name2 {0[0]} value2
|
|
[section3]
|
|
name3 {0[0]} value3
|
|
""".format(self.delimiters), defaults={"nameD": "valueD"})
|
|
self.assertEqual(set(cf['section1'].keys()), {'name1', 'named'})
|
|
self.assertEqual(set(cf['section2'].keys()), {'name2', 'named'})
|
|
self.assertEqual(set(cf['section3'].keys()), {'name3', 'named'})
|
|
self.assertEqual(cf['section1']['name1'], 'value1')
|
|
self.assertEqual(cf['section2']['name2'], 'value2')
|
|
self.assertEqual(cf['section3']['name3'], 'value3')
|
|
self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
|
|
cf['section2'] = {'name22': 'value22'}
|
|
self.assertEqual(set(cf['section2'].keys()), {'name22', 'named'})
|
|
self.assertEqual(cf['section2']['name22'], 'value22')
|
|
self.assertNotIn('name2', cf['section2'])
|
|
self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
|
|
cf['section3'] = {}
|
|
self.assertEqual(set(cf['section3'].keys()), {'named'})
|
|
self.assertNotIn('name3', cf['section3'])
|
|
self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
|
|
# For bpo-32108, assigning default_section to itself.
|
|
cf[self.default_section] = cf[self.default_section]
|
|
self.assertNotEqual(set(cf[self.default_section].keys()), set())
|
|
cf[self.default_section] = {}
|
|
self.assertEqual(set(cf[self.default_section].keys()), set())
|
|
self.assertEqual(set(cf['section1'].keys()), {'name1'})
|
|
self.assertEqual(set(cf['section2'].keys()), {'name22'})
|
|
self.assertEqual(set(cf['section3'].keys()), set())
|
|
self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
|
|
# For bpo-32108, assigning section to itself.
|
|
cf['section2'] = cf['section2']
|
|
self.assertEqual(set(cf['section2'].keys()), {'name22'})
|
|
|
|
def test_invalid_multiline_value(self):
|
|
if self.allow_no_value:
|
|
self.skipTest('if no_value is allowed, ParsingError is not raised')
|
|
|
|
invalid = textwrap.dedent("""\
|
|
[DEFAULT]
|
|
test {0} test
|
|
invalid""".format(self.delimiters[0])
|
|
)
|
|
cf = self.newconfig()
|
|
with self.assertRaises(configparser.ParsingError):
|
|
cf.read_string(invalid)
|
|
self.assertEqual(cf.get('DEFAULT', 'test'), 'test')
|
|
self.assertEqual(cf['DEFAULT']['test'], 'test')
|
|
|
|
|
|
class StrictTestCase(BasicTestCase, unittest.TestCase):
|
|
config_class = configparser.RawConfigParser
|
|
strict = True
|
|
|
|
|
|
class ConfigParserTestCase(BasicTestCase, unittest.TestCase):
|
|
config_class = configparser.ConfigParser
|
|
|
|
def test_interpolation(self):
|
|
cf = self.get_interpolation_config()
|
|
eq = self.assertEqual
|
|
eq(cf.get("Foo", "bar"), "something with interpolation (1 step)")
|
|
eq(cf.get("Foo", "bar9"),
|
|
"something with lots of interpolation (9 steps)")
|
|
eq(cf.get("Foo", "bar10"),
|
|
"something with lots of interpolation (10 steps)")
|
|
e = self.get_error(cf, configparser.InterpolationDepthError, "Foo", "bar11")
|
|
if self.interpolation == configparser._UNSET:
|
|
self.assertEqual(e.args, ("bar11", "Foo",
|
|
"something %(with11)s lots of interpolation (11 steps)"))
|
|
|
|
def test_interpolation_missing_value(self):
|
|
cf = self.get_interpolation_config()
|
|
e = self.get_error(cf, configparser.InterpolationMissingOptionError,
|
|
"Interpolation Error", "name")
|
|
self.assertEqual(e.reference, "reference")
|
|
self.assertEqual(e.section, "Interpolation Error")
|
|
self.assertEqual(e.option, "name")
|
|
if self.interpolation == configparser._UNSET:
|
|
self.assertEqual(e.args, ('name', 'Interpolation Error',
|
|
'%(reference)s', 'reference'))
|
|
|
|
def test_items(self):
|
|
self.check_items_config([('default', '<default>'),
|
|
('getdefault', '|<default>|'),
|
|
('key', '|value|'),
|
|
('name', 'value')])
|
|
|
|
def test_safe_interpolation(self):
|
|
# See https://bugs.python.org/issue511737
|
|
cf = self.fromstring("[section]\n"
|
|
"option1{eq}xxx\n"
|
|
"option2{eq}%(option1)s/xxx\n"
|
|
"ok{eq}%(option1)s/%%s\n"
|
|
"not_ok{eq}%(option2)s/%%s".format(
|
|
eq=self.delimiters[0]))
|
|
self.assertEqual(cf.get("section", "ok"), "xxx/%s")
|
|
if self.interpolation == configparser._UNSET:
|
|
self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s")
|
|
|
|
def test_set_malformatted_interpolation(self):
|
|
cf = self.fromstring("[sect]\n"
|
|
"option1{eq}foo\n".format(eq=self.delimiters[0]))
|
|
|
|
self.assertEqual(cf.get('sect', "option1"), "foo")
|
|
|
|
self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo")
|
|
self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%")
|
|
self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo")
|
|
|
|
self.assertEqual(cf.get('sect', "option1"), "foo")
|
|
|
|
# bug #5741: double percents are *not* malformed
|
|
cf.set("sect", "option2", "foo%%bar")
|
|
self.assertEqual(cf.get("sect", "option2"), "foo%bar")
|
|
|
|
def test_set_nonstring_types(self):
|
|
cf = self.fromstring("[sect]\n"
|
|
"option1{eq}foo\n".format(eq=self.delimiters[0]))
|
|
# Check that we get a TypeError when setting non-string values
|
|
# in an existing section:
|
|
self.assertRaises(TypeError, cf.set, "sect", "option1", 1)
|
|
self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0)
|
|
self.assertRaises(TypeError, cf.set, "sect", "option1", object())
|
|
self.assertRaises(TypeError, cf.set, "sect", "option2", 1)
|
|
self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0)
|
|
self.assertRaises(TypeError, cf.set, "sect", "option2", object())
|
|
self.assertRaises(TypeError, cf.set, "sect", 123, "invalid opt name!")
|
|
self.assertRaises(TypeError, cf.add_section, 123)
|
|
|
|
def test_add_section_default(self):
|
|
cf = self.newconfig()
|
|
self.assertRaises(ValueError, cf.add_section, self.default_section)
|
|
|
|
def test_defaults_keyword(self):
|
|
"""bpo-23835 fix for ConfigParser"""
|
|
cf = self.newconfig(defaults={1: 2.4})
|
|
self.assertEqual(cf[self.default_section]['1'], '2.4')
|
|
self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
|
|
cf = self.newconfig(defaults={"A": 5.2})
|
|
self.assertEqual(cf[self.default_section]['a'], '5.2')
|
|
self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
|
|
|
|
|
|
class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase):
|
|
config_class = configparser.ConfigParser
|
|
interpolation = None
|
|
ini = textwrap.dedent("""
|
|
[numbers]
|
|
one = 1
|
|
two = %(one)s * 2
|
|
three = ${common:one} * 3
|
|
|
|
[hexen]
|
|
sixteen = ${numbers:two} * 8
|
|
""").strip()
|
|
|
|
def assertMatchesIni(self, cf):
|
|
self.assertEqual(cf['numbers']['one'], '1')
|
|
self.assertEqual(cf['numbers']['two'], '%(one)s * 2')
|
|
self.assertEqual(cf['numbers']['three'], '${common:one} * 3')
|
|
self.assertEqual(cf['hexen']['sixteen'], '${numbers:two} * 8')
|
|
|
|
def test_no_interpolation(self):
|
|
cf = self.fromstring(self.ini)
|
|
self.assertMatchesIni(cf)
|
|
|
|
def test_empty_case(self):
|
|
cf = self.newconfig()
|
|
self.assertIsNone(cf.read_string(""))
|
|
|
|
def test_none_as_default_interpolation(self):
|
|
class CustomConfigParser(configparser.ConfigParser):
|
|
_DEFAULT_INTERPOLATION = None
|
|
|
|
cf = CustomConfigParser()
|
|
cf.read_string(self.ini)
|
|
self.assertMatchesIni(cf)
|
|
|
|
class ConfigParserTestCaseInvalidInterpolationType(unittest.TestCase):
|
|
def test_error_on_wrong_type_for_interpolation(self):
|
|
for value in [configparser.ExtendedInterpolation, 42, "a string"]:
|
|
with self.subTest(value=value):
|
|
with self.assertRaises(TypeError):
|
|
configparser.ConfigParser(interpolation=value)
|
|
|
|
|
|
class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase):
|
|
delimiters = (':=', '$')
|
|
comment_prefixes = ('//', '"')
|
|
inline_comment_prefixes = ('//', '"')
|
|
|
|
|
|
class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase):
|
|
default_section = 'general'
|
|
|
|
|
|
class MultilineValuesTestCase(BasicTestCase, unittest.TestCase):
|
|
config_class = configparser.ConfigParser
|
|
wonderful_spam = ("I'm having spam spam spam spam "
|
|
"spam spam spam beaked beans spam "
|
|
"spam spam and spam!").replace(' ', '\t\n')
|
|
|
|
def setUp(self):
|
|
cf = self.newconfig()
|
|
for i in range(100):
|
|
s = 'section{}'.format(i)
|
|
cf.add_section(s)
|
|
for j in range(10):
|
|
cf.set(s, 'lovely_spam{}'.format(j), self.wonderful_spam)
|
|
with open(os_helper.TESTFN, 'w', encoding="utf-8") as f:
|
|
cf.write(f)
|
|
|
|
def tearDown(self):
|
|
os.unlink(os_helper.TESTFN)
|
|
|
|
def test_dominating_multiline_values(self):
|
|
# We're reading from file because this is where the code changed
|
|
# during performance updates in Python 3.2
|
|
cf_from_file = self.newconfig()
|
|
with open(os_helper.TESTFN, encoding="utf-8") as f:
|
|
cf_from_file.read_file(f)
|
|
self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'),
|
|
self.wonderful_spam.replace('\t\n', '\n'))
|
|
|
|
|
|
class RawConfigParserTestCase(BasicTestCase, unittest.TestCase):
|
|
config_class = configparser.RawConfigParser
|
|
|
|
def test_interpolation(self):
|
|
cf = self.get_interpolation_config()
|
|
eq = self.assertEqual
|
|
eq(cf.get("Foo", "bar"),
|
|
"something %(with1)s interpolation (1 step)")
|
|
eq(cf.get("Foo", "bar9"),
|
|
"something %(with9)s lots of interpolation (9 steps)")
|
|
eq(cf.get("Foo", "bar10"),
|
|
"something %(with10)s lots of interpolation (10 steps)")
|
|
eq(cf.get("Foo", "bar11"),
|
|
"something %(with11)s lots of interpolation (11 steps)")
|
|
|
|
def test_items(self):
|
|
self.check_items_config([('default', '<default>'),
|
|
('getdefault', '|%(default)s|'),
|
|
('key', '|%(name)s|'),
|
|
('name', '%(value)s')])
|
|
|
|
def test_set_nonstring_types(self):
|
|
cf = self.newconfig()
|
|
cf.add_section('non-string')
|
|
cf.set('non-string', 'int', 1)
|
|
cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13])
|
|
cf.set('non-string', 'dict', {'pi': 3.14159})
|
|
self.assertEqual(cf.get('non-string', 'int'), 1)
|
|
self.assertEqual(cf.get('non-string', 'list'),
|
|
[0, 1, 1, 2, 3, 5, 8, 13])
|
|
self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159})
|
|
cf.add_section(123)
|
|
cf.set(123, 'this is sick', True)
|
|
self.assertEqual(cf.get(123, 'this is sick'), True)
|
|
if cf._dict is configparser._default_dict:
|
|
# would not work for SortedDict; only checking for the most common
|
|
# default dictionary (dict)
|
|
cf.optionxform = lambda x: x
|
|
cf.set('non-string', 1, 1)
|
|
self.assertEqual(cf.get('non-string', 1), 1)
|
|
|
|
def test_defaults_keyword(self):
|
|
"""bpo-23835 legacy behavior for RawConfigParser"""
|
|
with self.assertRaises(AttributeError) as ctx:
|
|
self.newconfig(defaults={1: 2.4})
|
|
err = ctx.exception
|
|
self.assertEqual(str(err), "'int' object has no attribute 'lower'")
|
|
cf = self.newconfig(defaults={"A": 5.2})
|
|
self.assertAlmostEqual(cf[self.default_section]['a'], 5.2)
|
|
|
|
|
|
class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
|
|
delimiters = (':=', '$')
|
|
comment_prefixes = ('//', '"')
|
|
inline_comment_prefixes = ('//', '"')
|
|
|
|
|
|
class RawConfigParserTestSambaConf(CfgParserTestCaseClass, unittest.TestCase):
|
|
config_class = configparser.RawConfigParser
|
|
comment_prefixes = ('#', ';', '----')
|
|
inline_comment_prefixes = ('//',)
|
|
empty_lines_in_values = False
|
|
|
|
def test_reading(self):
|
|
smbconf = support.findfile("cfgparser.2", subdir="configdata")
|
|
# check when we pass a mix of readable and non-readable files:
|
|
cf = self.newconfig()
|
|
parsed_files = cf.read([smbconf, "nonexistent-file"], encoding='utf-8')
|
|
self.assertEqual(parsed_files, [smbconf])
|
|
sections = ['global', 'homes', 'printers',
|
|
'print$', 'pdf-generator', 'tmp', 'Agustin']
|
|
self.assertEqual(cf.sections(), sections)
|
|
self.assertEqual(cf.get("global", "workgroup"), "MDKGROUP")
|
|
self.assertEqual(cf.getint("global", "max log size"), 50)
|
|
self.assertEqual(cf.get("global", "hosts allow"), "127.")
|
|
self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s")
|
|
|
|
class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase):
|
|
config_class = configparser.ConfigParser
|
|
interpolation = configparser.ExtendedInterpolation()
|
|
default_section = 'common'
|
|
strict = True
|
|
|
|
def fromstring(self, string, defaults=None, optionxform=None):
|
|
cf = self.newconfig(defaults)
|
|
if optionxform:
|
|
cf.optionxform = optionxform
|
|
cf.read_string(string)
|
|
return cf
|
|
|
|
def test_extended_interpolation(self):
|
|
cf = self.fromstring(textwrap.dedent("""
|
|
[common]
|
|
favourite Beatle = Paul
|
|
favourite color = green
|
|
|
|
[tom]
|
|
favourite band = ${favourite color} day
|
|
favourite pope = John ${favourite Beatle} II
|
|
sequel = ${favourite pope}I
|
|
|
|
[ambv]
|
|
favourite Beatle = George
|
|
son of Edward VII = ${favourite Beatle} V
|
|
son of George V = ${son of Edward VII}I
|
|
|
|
[stanley]
|
|
favourite Beatle = ${ambv:favourite Beatle}
|
|
favourite pope = ${tom:favourite pope}
|
|
favourite color = black
|
|
favourite state of mind = paranoid
|
|
favourite movie = soylent ${common:favourite color}
|
|
favourite song = ${favourite color} sabbath - ${favourite state of mind}
|
|
""").strip())
|
|
|
|
eq = self.assertEqual
|
|
eq(cf['common']['favourite Beatle'], 'Paul')
|
|
eq(cf['common']['favourite color'], 'green')
|
|
eq(cf['tom']['favourite Beatle'], 'Paul')
|
|
eq(cf['tom']['favourite color'], 'green')
|
|
eq(cf['tom']['favourite band'], 'green day')
|
|
eq(cf['tom']['favourite pope'], 'John Paul II')
|
|
eq(cf['tom']['sequel'], 'John Paul III')
|
|
eq(cf['ambv']['favourite Beatle'], 'George')
|
|
eq(cf['ambv']['favourite color'], 'green')
|
|
eq(cf['ambv']['son of Edward VII'], 'George V')
|
|
eq(cf['ambv']['son of George V'], 'George VI')
|
|
eq(cf['stanley']['favourite Beatle'], 'George')
|
|
eq(cf['stanley']['favourite color'], 'black')
|
|
eq(cf['stanley']['favourite state of mind'], 'paranoid')
|
|
eq(cf['stanley']['favourite movie'], 'soylent green')
|
|
eq(cf['stanley']['favourite pope'], 'John Paul II')
|
|
eq(cf['stanley']['favourite song'],
|
|
'black sabbath - paranoid')
|
|
|
|
def test_endless_loop(self):
|
|
cf = self.fromstring(textwrap.dedent("""
|
|
[one for you]
|
|
ping = ${one for me:pong}
|
|
|
|
[one for me]
|
|
pong = ${one for you:ping}
|
|
|
|
[selfish]
|
|
me = ${me}
|
|
""").strip())
|
|
|
|
with self.assertRaises(configparser.InterpolationDepthError):
|
|
cf['one for you']['ping']
|
|
with self.assertRaises(configparser.InterpolationDepthError):
|
|
cf['selfish']['me']
|
|
|
|
def test_strange_options(self):
|
|
cf = self.fromstring("""
|
|
[dollars]
|
|
$var = $$value
|
|
$var2 = ${$var}
|
|
${sick} = cannot interpolate me
|
|
|
|
[interpolated]
|
|
$other = ${dollars:$var}
|
|
$trying = ${dollars:${sick}}
|
|
""")
|
|
|
|
self.assertEqual(cf['dollars']['$var'], '$value')
|
|
self.assertEqual(cf['interpolated']['$other'], '$value')
|
|
self.assertEqual(cf['dollars']['${sick}'], 'cannot interpolate me')
|
|
exception_class = configparser.InterpolationMissingOptionError
|
|
with self.assertRaises(exception_class) as cm:
|
|
cf['interpolated']['$trying']
|
|
self.assertEqual(cm.exception.reference, 'dollars:${sick')
|
|
self.assertEqual(cm.exception.args[2], '${dollars:${sick}}') #rawval
|
|
|
|
def test_case_sensitivity_basic(self):
|
|
ini = textwrap.dedent("""
|
|
[common]
|
|
optionlower = value
|
|
OptionUpper = Value
|
|
|
|
[Common]
|
|
optionlower = a better ${common:optionlower}
|
|
OptionUpper = A Better ${common:OptionUpper}
|
|
|
|
[random]
|
|
foolower = ${common:optionlower} redefined
|
|
FooUpper = ${Common:OptionUpper} Redefined
|
|
""").strip()
|
|
|
|
cf = self.fromstring(ini)
|
|
eq = self.assertEqual
|
|
eq(cf['common']['optionlower'], 'value')
|
|
eq(cf['common']['OptionUpper'], 'Value')
|
|
eq(cf['Common']['optionlower'], 'a better value')
|
|
eq(cf['Common']['OptionUpper'], 'A Better Value')
|
|
eq(cf['random']['foolower'], 'value redefined')
|
|
eq(cf['random']['FooUpper'], 'A Better Value Redefined')
|
|
|
|
def test_case_sensitivity_conflicts(self):
|
|
ini = textwrap.dedent("""
|
|
[common]
|
|
option = value
|
|
Option = Value
|
|
|
|
[Common]
|
|
option = a better ${common:option}
|
|
Option = A Better ${common:Option}
|
|
|
|
[random]
|
|
foo = ${common:option} redefined
|
|
Foo = ${Common:Option} Redefined
|
|
""").strip()
|
|
with self.assertRaises(configparser.DuplicateOptionError):
|
|
cf = self.fromstring(ini)
|
|
|
|
# raw options
|
|
cf = self.fromstring(ini, optionxform=lambda opt: opt)
|
|
eq = self.assertEqual
|
|
eq(cf['common']['option'], 'value')
|
|
eq(cf['common']['Option'], 'Value')
|
|
eq(cf['Common']['option'], 'a better value')
|
|
eq(cf['Common']['Option'], 'A Better Value')
|
|
eq(cf['random']['foo'], 'value redefined')
|
|
eq(cf['random']['Foo'], 'A Better Value Redefined')
|
|
|
|
def test_other_errors(self):
|
|
cf = self.fromstring("""
|
|
[interpolation fail]
|
|
case1 = ${where's the brace
|
|
case2 = ${does_not_exist}
|
|
case3 = ${wrong_section:wrong_value}
|
|
case4 = ${i:like:colon:characters}
|
|
case5 = $100 for Fail No 5!
|
|
""")
|
|
|
|
with self.assertRaises(configparser.InterpolationSyntaxError):
|
|
cf['interpolation fail']['case1']
|
|
with self.assertRaises(configparser.InterpolationMissingOptionError):
|
|
cf['interpolation fail']['case2']
|
|
with self.assertRaises(configparser.InterpolationMissingOptionError):
|
|
cf['interpolation fail']['case3']
|
|
with self.assertRaises(configparser.InterpolationSyntaxError):
|
|
cf['interpolation fail']['case4']
|
|
with self.assertRaises(configparser.InterpolationSyntaxError):
|
|
cf['interpolation fail']['case5']
|
|
with self.assertRaises(ValueError):
|
|
cf['interpolation fail']['case6'] = "BLACK $ABBATH"
|
|
|
|
|
|
class ConfigParserTestCaseNoValue(ConfigParserTestCase):
|
|
allow_no_value = True
|
|
|
|
|
|
class ConfigParserTestCaseTrickyFile(CfgParserTestCaseClass, unittest.TestCase):
|
|
config_class = configparser.ConfigParser
|
|
delimiters = {'='}
|
|
comment_prefixes = {'#'}
|
|
allow_no_value = True
|
|
|
|
def test_cfgparser_dot_3(self):
|
|
tricky = support.findfile("cfgparser.3", subdir="configdata")
|
|
cf = self.newconfig()
|
|
self.assertEqual(len(cf.read(tricky, encoding='utf-8')), 1)
|
|
self.assertEqual(cf.sections(), ['strange',
|
|
'corruption',
|
|
'yeah, sections can be '
|
|
'indented as well',
|
|
'another one!',
|
|
'no values here',
|
|
'tricky interpolation',
|
|
'more interpolation'])
|
|
self.assertEqual(cf.getint(self.default_section, 'go',
|
|
vars={'interpolate': '-1'}), -1)
|
|
with self.assertRaises(ValueError):
|
|
# no interpolation will happen
|
|
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('corruption', 'value').split('\n')), 10)
|
|
longname = 'yeah, sections can be indented as well'
|
|
self.assertFalse(cf.getboolean(longname, 'are they subsections'))
|
|
self.assertEqual(cf.get(longname, 'lets use some Unicode'), '片仮名')
|
|
self.assertEqual(len(cf.items('another one!')), 5) # 4 in section and
|
|
# `go` from DEFAULT
|
|
with self.assertRaises(configparser.InterpolationMissingOptionError):
|
|
cf.items('no values here')
|
|
self.assertEqual(cf.get('tricky interpolation', 'lets'), 'do this')
|
|
self.assertEqual(cf.get('tricky interpolation', 'lets'),
|
|
cf.get('tricky interpolation', 'go'))
|
|
self.assertEqual(cf.get('more interpolation', 'lets'), 'go shopping')
|
|
|
|
def test_unicode_failure(self):
|
|
tricky = support.findfile("cfgparser.3", subdir="configdata")
|
|
cf = self.newconfig()
|
|
with self.assertRaises(UnicodeDecodeError):
|
|
cf.read(tricky, encoding='ascii')
|
|
|
|
|
|
class Issue7005TestCase(unittest.TestCase):
|
|
"""Test output when None is set() as a value and allow_no_value == False.
|
|
|
|
http://bugs.python.org/issue7005
|
|
|
|
"""
|
|
|
|
expected_output = "[section]\noption = None\n\n"
|
|
|
|
def prepare(self, config_class):
|
|
# This is the default, but that's the point.
|
|
cp = config_class(allow_no_value=False)
|
|
cp.add_section("section")
|
|
cp.set("section", "option", None)
|
|
sio = io.StringIO()
|
|
cp.write(sio)
|
|
return sio.getvalue()
|
|
|
|
def test_none_as_value_stringified(self):
|
|
cp = configparser.ConfigParser(allow_no_value=False)
|
|
cp.add_section("section")
|
|
with self.assertRaises(TypeError):
|
|
cp.set("section", "option", None)
|
|
|
|
def test_none_as_value_stringified_raw(self):
|
|
output = self.prepare(configparser.RawConfigParser)
|
|
self.assertEqual(output, self.expected_output)
|
|
|
|
|
|
class SortedTestCase(RawConfigParserTestCase):
|
|
dict_type = SortedDict
|
|
|
|
def test_sorted(self):
|
|
cf = self.fromstring("[b]\n"
|
|
"o4=1\n"
|
|
"o3=2\n"
|
|
"o2=3\n"
|
|
"o1=4\n"
|
|
"[a]\n"
|
|
"k=v\n")
|
|
output = io.StringIO()
|
|
cf.write(output)
|
|
self.assertEqual(output.getvalue(),
|
|
"[a]\n"
|
|
"k = v\n\n"
|
|
"[b]\n"
|
|
"o1 = 4\n"
|
|
"o2 = 3\n"
|
|
"o3 = 2\n"
|
|
"o4 = 1\n\n")
|
|
|
|
|
|
class CompatibleTestCase(CfgParserTestCaseClass, unittest.TestCase):
|
|
config_class = configparser.RawConfigParser
|
|
comment_prefixes = '#;'
|
|
inline_comment_prefixes = ';'
|
|
|
|
def test_comment_handling(self):
|
|
config_string = textwrap.dedent("""\
|
|
[Commented Bar]
|
|
baz=qwe ; a comment
|
|
foo: bar # not a comment!
|
|
# but this is a comment
|
|
; another comment
|
|
quirk: this;is not a comment
|
|
; a space must precede an inline comment
|
|
""")
|
|
cf = self.fromstring(config_string)
|
|
self.assertEqual(cf.get('Commented Bar', 'foo'),
|
|
'bar # not a comment!')
|
|
self.assertEqual(cf.get('Commented Bar', 'baz'), 'qwe')
|
|
self.assertEqual(cf.get('Commented Bar', 'quirk'),
|
|
'this;is not a comment')
|
|
|
|
class CopyTestCase(BasicTestCase, unittest.TestCase):
|
|
config_class = configparser.ConfigParser
|
|
|
|
def fromstring(self, string, defaults=None):
|
|
cf = self.newconfig(defaults)
|
|
cf.read_string(string)
|
|
cf_copy = self.newconfig()
|
|
cf_copy.read_dict(cf)
|
|
# we have to clean up option duplicates that appeared because of
|
|
# the magic DEFAULTSECT behaviour.
|
|
for section in cf_copy.values():
|
|
if section.name == self.default_section:
|
|
continue
|
|
for default, value in cf[self.default_section].items():
|
|
if section[default] == value:
|
|
del section[default]
|
|
return cf_copy
|
|
|
|
|
|
class FakeFile:
|
|
def __init__(self):
|
|
file_path = support.findfile("cfgparser.1", subdir="configdata")
|
|
with open(file_path, encoding="utf-8") as f:
|
|
self.lines = f.readlines()
|
|
self.lines.reverse()
|
|
|
|
def readline(self):
|
|
if len(self.lines):
|
|
return self.lines.pop()
|
|
return ''
|
|
|
|
|
|
def readline_generator(f):
|
|
"""As advised in Doc/library/configparser.rst."""
|
|
line = f.readline()
|
|
while line:
|
|
yield line
|
|
line = f.readline()
|
|
|
|
|
|
class ReadFileTestCase(unittest.TestCase):
|
|
def test_file(self):
|
|
file_paths = [support.findfile("cfgparser.1", subdir="configdata")]
|
|
try:
|
|
file_paths.append(file_paths[0].encode('utf8'))
|
|
except UnicodeEncodeError:
|
|
pass # unfortunately we can't test bytes on this path
|
|
for file_path in file_paths:
|
|
parser = configparser.ConfigParser()
|
|
with open(file_path, encoding="utf-8") as f:
|
|
parser.read_file(f)
|
|
self.assertIn("Foo Bar", parser)
|
|
self.assertIn("foo", parser["Foo Bar"])
|
|
self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
|
|
|
|
def test_iterable(self):
|
|
lines = textwrap.dedent("""
|
|
[Foo Bar]
|
|
foo=newbar""").strip().split('\n')
|
|
parser = configparser.ConfigParser()
|
|
parser.read_file(lines)
|
|
self.assertIn("Foo Bar", parser)
|
|
self.assertIn("foo", parser["Foo Bar"])
|
|
self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
|
|
|
|
def test_readline_generator(self):
|
|
"""Issue #11670."""
|
|
parser = configparser.ConfigParser()
|
|
with self.assertRaises(TypeError):
|
|
parser.read_file(FakeFile())
|
|
parser.read_file(readline_generator(FakeFile()))
|
|
self.assertIn("Foo Bar", parser)
|
|
self.assertIn("foo", parser["Foo Bar"])
|
|
self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
|
|
|
|
def test_source_as_bytes(self):
|
|
"""Issue #18260."""
|
|
lines = textwrap.dedent("""
|
|
[badbad]
|
|
[badbad]""").strip().split('\n')
|
|
parser = configparser.ConfigParser()
|
|
with self.assertRaises(configparser.DuplicateSectionError) as dse:
|
|
parser.read_file(lines, source=b"badbad")
|
|
self.assertEqual(
|
|
str(dse.exception),
|
|
"While reading from b'badbad' [line 2]: section 'badbad' "
|
|
"already exists"
|
|
)
|
|
lines = textwrap.dedent("""
|
|
[badbad]
|
|
bad = bad
|
|
bad = bad""").strip().split('\n')
|
|
parser = configparser.ConfigParser()
|
|
with self.assertRaises(configparser.DuplicateOptionError) as dse:
|
|
parser.read_file(lines, source=b"badbad")
|
|
self.assertEqual(
|
|
str(dse.exception),
|
|
"While reading from b'badbad' [line 3]: option 'bad' in section "
|
|
"'badbad' already exists"
|
|
)
|
|
lines = textwrap.dedent("""
|
|
[badbad]
|
|
= bad""").strip().split('\n')
|
|
parser = configparser.ConfigParser()
|
|
with self.assertRaises(configparser.ParsingError) as dse:
|
|
parser.read_file(lines, source=b"badbad")
|
|
self.assertEqual(
|
|
str(dse.exception),
|
|
"Source contains parsing errors: b'badbad'\n\t[line 2]: '= bad'"
|
|
)
|
|
lines = textwrap.dedent("""
|
|
[badbad
|
|
bad = bad""").strip().split('\n')
|
|
parser = configparser.ConfigParser()
|
|
with self.assertRaises(configparser.MissingSectionHeaderError) as dse:
|
|
parser.read_file(lines, source=b"badbad")
|
|
self.assertEqual(
|
|
str(dse.exception),
|
|
"File contains no section headers.\nfile: b'badbad', line: 1\n"
|
|
"'[badbad'"
|
|
)
|
|
|
|
def test_keys_without_value_with_extra_whitespace(self):
|
|
lines = [
|
|
'[SECT]\n',
|
|
'KEY1\n',
|
|
' KEY2 = VAL2\n', # note the Space before the key!
|
|
]
|
|
parser = configparser.ConfigParser(
|
|
comment_prefixes="",
|
|
allow_no_value=True,
|
|
strict=False,
|
|
delimiters=('=',),
|
|
interpolation=None,
|
|
)
|
|
with self.assertRaises(configparser.MultilineContinuationError) as dse:
|
|
parser.read_file(lines)
|
|
self.assertEqual(
|
|
str(dse.exception),
|
|
"Key without value continued with an indented line.\n"
|
|
"file: '<???>', line: 3\n"
|
|
"' KEY2 = VAL2\\n'"
|
|
)
|
|
|
|
|
|
|
|
|
|
class CoverageOneHundredTestCase(unittest.TestCase):
|
|
"""Covers edge cases in the codebase."""
|
|
|
|
def test_duplicate_option_error(self):
|
|
error = configparser.DuplicateOptionError('section', 'option')
|
|
self.assertEqual(error.section, 'section')
|
|
self.assertEqual(error.option, 'option')
|
|
self.assertEqual(error.source, None)
|
|
self.assertEqual(error.lineno, None)
|
|
self.assertEqual(error.args, ('section', 'option', None, None))
|
|
self.assertEqual(str(error), "Option 'option' in section 'section' "
|
|
"already exists")
|
|
|
|
def test_interpolation_depth_error(self):
|
|
error = configparser.InterpolationDepthError('option', 'section',
|
|
'rawval')
|
|
self.assertEqual(error.args, ('option', 'section', 'rawval'))
|
|
self.assertEqual(error.option, 'option')
|
|
self.assertEqual(error.section, 'section')
|
|
|
|
def test_parsing_error(self):
|
|
with self.assertRaises(TypeError) as cm:
|
|
configparser.ParsingError()
|
|
error = configparser.ParsingError(source='source')
|
|
self.assertEqual(error.source, 'source')
|
|
error = configparser.ParsingError('source')
|
|
self.assertEqual(error.source, 'source')
|
|
|
|
def test_interpolation_validation(self):
|
|
parser = configparser.ConfigParser()
|
|
parser.read_string("""
|
|
[section]
|
|
invalid_percent = %
|
|
invalid_reference = %(()
|
|
invalid_variable = %(does_not_exist)s
|
|
""")
|
|
with self.assertRaises(configparser.InterpolationSyntaxError) as cm:
|
|
parser['section']['invalid_percent']
|
|
self.assertEqual(str(cm.exception), "'%' must be followed by '%' or "
|
|
"'(', found: '%'")
|
|
with self.assertRaises(configparser.InterpolationSyntaxError) as cm:
|
|
parser['section']['invalid_reference']
|
|
self.assertEqual(str(cm.exception), "bad interpolation variable "
|
|
"reference '%(()'")
|
|
|
|
def test_sectionproxy_repr(self):
|
|
parser = configparser.ConfigParser()
|
|
parser.read_string("""
|
|
[section]
|
|
key = value
|
|
""")
|
|
self.assertEqual(repr(parser['section']), '<Section: section>')
|
|
|
|
def test_inconsistent_converters_state(self):
|
|
parser = configparser.ConfigParser()
|
|
import decimal
|
|
parser.converters['decimal'] = decimal.Decimal
|
|
parser.read_string("""
|
|
[s1]
|
|
one = 1
|
|
[s2]
|
|
two = 2
|
|
""")
|
|
self.assertIn('decimal', parser.converters)
|
|
self.assertEqual(parser.getdecimal('s1', 'one'), 1)
|
|
self.assertEqual(parser.getdecimal('s2', 'two'), 2)
|
|
self.assertEqual(parser['s1'].getdecimal('one'), 1)
|
|
self.assertEqual(parser['s2'].getdecimal('two'), 2)
|
|
del parser.getdecimal
|
|
with self.assertRaises(AttributeError):
|
|
parser.getdecimal('s1', 'one')
|
|
self.assertIn('decimal', parser.converters)
|
|
del parser.converters['decimal']
|
|
self.assertNotIn('decimal', parser.converters)
|
|
with self.assertRaises(AttributeError):
|
|
parser.getdecimal('s1', 'one')
|
|
with self.assertRaises(AttributeError):
|
|
parser['s1'].getdecimal('one')
|
|
with self.assertRaises(AttributeError):
|
|
parser['s2'].getdecimal('two')
|
|
|
|
|
|
class ExceptionPicklingTestCase(unittest.TestCase):
|
|
"""Tests for issue #13760: ConfigParser exceptions are not picklable."""
|
|
|
|
def test_error(self):
|
|
import pickle
|
|
e1 = configparser.Error('value')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_nosectionerror(self):
|
|
import pickle
|
|
e1 = configparser.NoSectionError('section')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.section, e2.section)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_nooptionerror(self):
|
|
import pickle
|
|
e1 = configparser.NoOptionError('option', 'section')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.section, e2.section)
|
|
self.assertEqual(e1.option, e2.option)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_duplicatesectionerror(self):
|
|
import pickle
|
|
e1 = configparser.DuplicateSectionError('section', 'source', 123)
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.section, e2.section)
|
|
self.assertEqual(e1.source, e2.source)
|
|
self.assertEqual(e1.lineno, e2.lineno)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_duplicateoptionerror(self):
|
|
import pickle
|
|
e1 = configparser.DuplicateOptionError('section', 'option', 'source',
|
|
123)
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.section, e2.section)
|
|
self.assertEqual(e1.option, e2.option)
|
|
self.assertEqual(e1.source, e2.source)
|
|
self.assertEqual(e1.lineno, e2.lineno)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_interpolationerror(self):
|
|
import pickle
|
|
e1 = configparser.InterpolationError('option', 'section', 'msg')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.section, e2.section)
|
|
self.assertEqual(e1.option, e2.option)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_interpolationmissingoptionerror(self):
|
|
import pickle
|
|
e1 = configparser.InterpolationMissingOptionError('option', 'section',
|
|
'rawval', 'reference')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.section, e2.section)
|
|
self.assertEqual(e1.option, e2.option)
|
|
self.assertEqual(e1.reference, e2.reference)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_interpolationsyntaxerror(self):
|
|
import pickle
|
|
e1 = configparser.InterpolationSyntaxError('option', 'section', 'msg')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.section, e2.section)
|
|
self.assertEqual(e1.option, e2.option)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_interpolationdeptherror(self):
|
|
import pickle
|
|
e1 = configparser.InterpolationDepthError('option', 'section',
|
|
'rawval')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.section, e2.section)
|
|
self.assertEqual(e1.option, e2.option)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_parsingerror(self):
|
|
import pickle
|
|
e1 = configparser.ParsingError('source')
|
|
e1.append(1, 'line1')
|
|
e1.append(2, 'line2')
|
|
e1.append(3, 'line3')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.source, e2.source)
|
|
self.assertEqual(e1.errors, e2.errors)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
e1 = configparser.ParsingError('filename')
|
|
e1.append(1, 'line1')
|
|
e1.append(2, 'line2')
|
|
e1.append(3, 'line3')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.source, e2.source)
|
|
self.assertEqual(e1.errors, e2.errors)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
def test_missingsectionheadererror(self):
|
|
import pickle
|
|
e1 = configparser.MissingSectionHeaderError('filename', 123, 'line')
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
pickled = pickle.dumps(e1, proto)
|
|
e2 = pickle.loads(pickled)
|
|
self.assertEqual(e1.message, e2.message)
|
|
self.assertEqual(e1.args, e2.args)
|
|
self.assertEqual(e1.line, e2.line)
|
|
self.assertEqual(e1.source, e2.source)
|
|
self.assertEqual(e1.lineno, e2.lineno)
|
|
self.assertEqual(repr(e1), repr(e2))
|
|
|
|
|
|
class InlineCommentStrippingTestCase(unittest.TestCase):
|
|
"""Tests for issue #14590: ConfigParser doesn't strip inline comment when
|
|
delimiter occurs earlier without preceding space.."""
|
|
|
|
def test_stripping(self):
|
|
cfg = configparser.ConfigParser(inline_comment_prefixes=(';', '#',
|
|
'//'))
|
|
cfg.read_string("""
|
|
[section]
|
|
k1 = v1;still v1
|
|
k2 = v2 ;a comment
|
|
k3 = v3 ; also a comment
|
|
k4 = v4;still v4 ;a comment
|
|
k5 = v5;still v5 ; also a comment
|
|
k6 = v6;still v6; and still v6 ;a comment
|
|
k7 = v7;still v7; and still v7 ; also a comment
|
|
|
|
[multiprefix]
|
|
k1 = v1;still v1 #a comment ; yeah, pretty much
|
|
k2 = v2 // this already is a comment ; continued
|
|
k3 = v3;#//still v3# and still v3 ; a comment
|
|
""")
|
|
s = cfg['section']
|
|
self.assertEqual(s['k1'], 'v1;still v1')
|
|
self.assertEqual(s['k2'], 'v2')
|
|
self.assertEqual(s['k3'], 'v3')
|
|
self.assertEqual(s['k4'], 'v4;still v4')
|
|
self.assertEqual(s['k5'], 'v5;still v5')
|
|
self.assertEqual(s['k6'], 'v6;still v6; and still v6')
|
|
self.assertEqual(s['k7'], 'v7;still v7; and still v7')
|
|
s = cfg['multiprefix']
|
|
self.assertEqual(s['k1'], 'v1;still v1')
|
|
self.assertEqual(s['k2'], 'v2')
|
|
self.assertEqual(s['k3'], 'v3;#//still v3# and still v3')
|
|
|
|
|
|
class ExceptionContextTestCase(unittest.TestCase):
|
|
""" Test that implementation details doesn't leak
|
|
through raising exceptions. """
|
|
|
|
def test_get_basic_interpolation(self):
|
|
parser = configparser.ConfigParser()
|
|
parser.read_string("""
|
|
[Paths]
|
|
home_dir: /Users
|
|
my_dir: %(home_dir1)s/lumberjack
|
|
my_pictures: %(my_dir)s/Pictures
|
|
""")
|
|
cm = self.assertRaises(configparser.InterpolationMissingOptionError)
|
|
with cm:
|
|
parser.get('Paths', 'my_dir')
|
|
self.assertIs(cm.exception.__suppress_context__, True)
|
|
|
|
def test_get_extended_interpolation(self):
|
|
parser = configparser.ConfigParser(
|
|
interpolation=configparser.ExtendedInterpolation())
|
|
parser.read_string("""
|
|
[Paths]
|
|
home_dir: /Users
|
|
my_dir: ${home_dir1}/lumberjack
|
|
my_pictures: ${my_dir}/Pictures
|
|
""")
|
|
cm = self.assertRaises(configparser.InterpolationMissingOptionError)
|
|
with cm:
|
|
parser.get('Paths', 'my_dir')
|
|
self.assertIs(cm.exception.__suppress_context__, True)
|
|
|
|
def test_missing_options(self):
|
|
parser = configparser.ConfigParser()
|
|
parser.read_string("""
|
|
[Paths]
|
|
home_dir: /Users
|
|
""")
|
|
with self.assertRaises(configparser.NoSectionError) as cm:
|
|
parser.options('test')
|
|
self.assertIs(cm.exception.__suppress_context__, True)
|
|
|
|
def test_missing_section(self):
|
|
config = configparser.ConfigParser()
|
|
with self.assertRaises(configparser.NoSectionError) as cm:
|
|
config.set('Section1', 'an_int', '15')
|
|
self.assertIs(cm.exception.__suppress_context__, True)
|
|
|
|
def test_remove_option(self):
|
|
config = configparser.ConfigParser()
|
|
with self.assertRaises(configparser.NoSectionError) as cm:
|
|
config.remove_option('Section1', 'an_int')
|
|
self.assertIs(cm.exception.__suppress_context__, True)
|
|
|
|
|
|
class ConvertersTestCase(BasicTestCase, unittest.TestCase):
|
|
"""Introduced in 3.5, issue #18159."""
|
|
|
|
config_class = configparser.ConfigParser
|
|
|
|
def newconfig(self, defaults=None):
|
|
instance = super().newconfig(defaults=defaults)
|
|
instance.converters['list'] = lambda v: [e.strip() for e in v.split()
|
|
if e.strip()]
|
|
return instance
|
|
|
|
def test_converters(self):
|
|
cfg = self.newconfig()
|
|
self.assertIn('boolean', cfg.converters)
|
|
self.assertIn('list', cfg.converters)
|
|
self.assertIsNone(cfg.converters['int'])
|
|
self.assertIsNone(cfg.converters['float'])
|
|
self.assertIsNone(cfg.converters['boolean'])
|
|
self.assertIsNotNone(cfg.converters['list'])
|
|
self.assertEqual(len(cfg.converters), 4)
|
|
with self.assertRaises(ValueError):
|
|
cfg.converters[''] = lambda v: v
|
|
with self.assertRaises(ValueError):
|
|
cfg.converters[None] = lambda v: v
|
|
cfg.read_string("""
|
|
[s]
|
|
str = string
|
|
int = 1
|
|
float = 0.5
|
|
list = a b c d e f g
|
|
bool = yes
|
|
""")
|
|
s = cfg['s']
|
|
self.assertEqual(s['str'], 'string')
|
|
self.assertEqual(s['int'], '1')
|
|
self.assertEqual(s['float'], '0.5')
|
|
self.assertEqual(s['list'], 'a b c d e f g')
|
|
self.assertEqual(s['bool'], 'yes')
|
|
self.assertEqual(cfg.get('s', 'str'), 'string')
|
|
self.assertEqual(cfg.get('s', 'int'), '1')
|
|
self.assertEqual(cfg.get('s', 'float'), '0.5')
|
|
self.assertEqual(cfg.get('s', 'list'), 'a b c d e f g')
|
|
self.assertEqual(cfg.get('s', 'bool'), 'yes')
|
|
self.assertEqual(cfg.get('s', 'str'), 'string')
|
|
self.assertEqual(cfg.getint('s', 'int'), 1)
|
|
self.assertEqual(cfg.getfloat('s', 'float'), 0.5)
|
|
self.assertEqual(cfg.getlist('s', 'list'), ['a', 'b', 'c', 'd',
|
|
'e', 'f', 'g'])
|
|
self.assertEqual(cfg.getboolean('s', 'bool'), True)
|
|
self.assertEqual(s.get('str'), 'string')
|
|
self.assertEqual(s.getint('int'), 1)
|
|
self.assertEqual(s.getfloat('float'), 0.5)
|
|
self.assertEqual(s.getlist('list'), ['a', 'b', 'c', 'd',
|
|
'e', 'f', 'g'])
|
|
self.assertEqual(s.getboolean('bool'), True)
|
|
with self.assertRaises(AttributeError):
|
|
cfg.getdecimal('s', 'float')
|
|
with self.assertRaises(AttributeError):
|
|
s.getdecimal('float')
|
|
import decimal
|
|
cfg.converters['decimal'] = decimal.Decimal
|
|
self.assertIn('decimal', cfg.converters)
|
|
self.assertIsNotNone(cfg.converters['decimal'])
|
|
self.assertEqual(len(cfg.converters), 5)
|
|
dec0_5 = decimal.Decimal('0.5')
|
|
self.assertEqual(cfg.getdecimal('s', 'float'), dec0_5)
|
|
self.assertEqual(s.getdecimal('float'), dec0_5)
|
|
del cfg.converters['decimal']
|
|
self.assertNotIn('decimal', cfg.converters)
|
|
self.assertEqual(len(cfg.converters), 4)
|
|
with self.assertRaises(AttributeError):
|
|
cfg.getdecimal('s', 'float')
|
|
with self.assertRaises(AttributeError):
|
|
s.getdecimal('float')
|
|
with self.assertRaises(KeyError):
|
|
del cfg.converters['decimal']
|
|
with self.assertRaises(KeyError):
|
|
del cfg.converters['']
|
|
with self.assertRaises(KeyError):
|
|
del cfg.converters[None]
|
|
|
|
|
|
class BlatantOverrideConvertersTestCase(unittest.TestCase):
|
|
"""What if somebody overrode a getboolean()? We want to make sure that in
|
|
this case the automatic converters do not kick in."""
|
|
|
|
config = """
|
|
[one]
|
|
one = false
|
|
two = false
|
|
three = long story short
|
|
|
|
[two]
|
|
one = false
|
|
two = false
|
|
three = four
|
|
"""
|
|
|
|
def test_converters_at_init(self):
|
|
cfg = configparser.ConfigParser(converters={'len': len})
|
|
cfg.read_string(self.config)
|
|
self._test_len(cfg)
|
|
self.assertIsNotNone(cfg.converters['len'])
|
|
|
|
def test_inheritance(self):
|
|
class StrangeConfigParser(configparser.ConfigParser):
|
|
gettysburg = 'a historic borough in south central Pennsylvania'
|
|
|
|
def getboolean(self, section, option, *, raw=False, vars=None,
|
|
fallback=configparser._UNSET):
|
|
if section == option:
|
|
return True
|
|
return super().getboolean(section, option, raw=raw, vars=vars,
|
|
fallback=fallback)
|
|
def getlen(self, section, option, *, raw=False, vars=None,
|
|
fallback=configparser._UNSET):
|
|
return self._get_conv(section, option, len, raw=raw, vars=vars,
|
|
fallback=fallback)
|
|
|
|
cfg = StrangeConfigParser()
|
|
cfg.read_string(self.config)
|
|
self._test_len(cfg)
|
|
self.assertIsNone(cfg.converters['len'])
|
|
self.assertTrue(cfg.getboolean('one', 'one'))
|
|
self.assertTrue(cfg.getboolean('two', 'two'))
|
|
self.assertFalse(cfg.getboolean('one', 'two'))
|
|
self.assertFalse(cfg.getboolean('two', 'one'))
|
|
cfg.converters['boolean'] = cfg._convert_to_boolean
|
|
self.assertFalse(cfg.getboolean('one', 'one'))
|
|
self.assertFalse(cfg.getboolean('two', 'two'))
|
|
self.assertFalse(cfg.getboolean('one', 'two'))
|
|
self.assertFalse(cfg.getboolean('two', 'one'))
|
|
|
|
def _test_len(self, cfg):
|
|
self.assertEqual(len(cfg.converters), 4)
|
|
self.assertIn('boolean', cfg.converters)
|
|
self.assertIn('len', cfg.converters)
|
|
self.assertNotIn('tysburg', cfg.converters)
|
|
self.assertIsNone(cfg.converters['int'])
|
|
self.assertIsNone(cfg.converters['float'])
|
|
self.assertIsNone(cfg.converters['boolean'])
|
|
self.assertEqual(cfg.getlen('one', 'one'), 5)
|
|
self.assertEqual(cfg.getlen('one', 'two'), 5)
|
|
self.assertEqual(cfg.getlen('one', 'three'), 16)
|
|
self.assertEqual(cfg.getlen('two', 'one'), 5)
|
|
self.assertEqual(cfg.getlen('two', 'two'), 5)
|
|
self.assertEqual(cfg.getlen('two', 'three'), 4)
|
|
self.assertEqual(cfg.getlen('two', 'four', fallback=0), 0)
|
|
with self.assertRaises(configparser.NoOptionError):
|
|
cfg.getlen('two', 'four')
|
|
self.assertEqual(cfg['one'].getlen('one'), 5)
|
|
self.assertEqual(cfg['one'].getlen('two'), 5)
|
|
self.assertEqual(cfg['one'].getlen('three'), 16)
|
|
self.assertEqual(cfg['two'].getlen('one'), 5)
|
|
self.assertEqual(cfg['two'].getlen('two'), 5)
|
|
self.assertEqual(cfg['two'].getlen('three'), 4)
|
|
self.assertEqual(cfg['two'].getlen('four', 0), 0)
|
|
self.assertEqual(cfg['two'].getlen('four'), None)
|
|
|
|
def test_instance_assignment(self):
|
|
cfg = configparser.ConfigParser()
|
|
cfg.getboolean = lambda section, option: True
|
|
cfg.getlen = lambda section, option: len(cfg[section][option])
|
|
cfg.read_string(self.config)
|
|
self.assertEqual(len(cfg.converters), 3)
|
|
self.assertIn('boolean', cfg.converters)
|
|
self.assertNotIn('len', cfg.converters)
|
|
self.assertIsNone(cfg.converters['int'])
|
|
self.assertIsNone(cfg.converters['float'])
|
|
self.assertIsNone(cfg.converters['boolean'])
|
|
self.assertTrue(cfg.getboolean('one', 'one'))
|
|
self.assertTrue(cfg.getboolean('two', 'two'))
|
|
self.assertTrue(cfg.getboolean('one', 'two'))
|
|
self.assertTrue(cfg.getboolean('two', 'one'))
|
|
cfg.converters['boolean'] = cfg._convert_to_boolean
|
|
self.assertFalse(cfg.getboolean('one', 'one'))
|
|
self.assertFalse(cfg.getboolean('two', 'two'))
|
|
self.assertFalse(cfg.getboolean('one', 'two'))
|
|
self.assertFalse(cfg.getboolean('two', 'one'))
|
|
self.assertEqual(cfg.getlen('one', 'one'), 5)
|
|
self.assertEqual(cfg.getlen('one', 'two'), 5)
|
|
self.assertEqual(cfg.getlen('one', 'three'), 16)
|
|
self.assertEqual(cfg.getlen('two', 'one'), 5)
|
|
self.assertEqual(cfg.getlen('two', 'two'), 5)
|
|
self.assertEqual(cfg.getlen('two', 'three'), 4)
|
|
# If a getter impl is assigned straight to the instance, it won't
|
|
# be available on the section proxies.
|
|
with self.assertRaises(AttributeError):
|
|
self.assertEqual(cfg['one'].getlen('one'), 5)
|
|
with self.assertRaises(AttributeError):
|
|
self.assertEqual(cfg['two'].getlen('one'), 5)
|
|
|
|
|
|
class MiscTestCase(unittest.TestCase):
|
|
def test__all__(self):
|
|
support.check__all__(self, configparser, not_exported={"Error"})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|