Merged revisions 78232 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r78232 | fred.drake | 2010-02-19 00:24:30 -0500 (Fri, 19 Feb 2010) | 3 lines - apply patch from issue 7005 - add corresponding documentation ........
This commit is contained in:
parent
2c2dc37e58
commit
03c44a30a3
|
@ -56,19 +56,28 @@ dictionary type is passed that sorts its keys, the sections will be sorted on
|
||||||
write-back, as will be the keys within each section.
|
write-back, as will be the keys within each section.
|
||||||
|
|
||||||
|
|
||||||
.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict)
|
.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict,
|
||||||
|
allow_no_value=False)
|
||||||
|
|
||||||
The basic configuration object. When *defaults* is given, it is initialized
|
The basic configuration object. When *defaults* is given, it is initialized
|
||||||
into the dictionary of intrinsic defaults. When *dict_type* is given, it will
|
into the dictionary of intrinsic defaults. When *dict_type* is given, it will
|
||||||
be used to create the dictionary objects for the list of sections, for the
|
be used to create the dictionary objects for the list of sections, for the
|
||||||
options within a section, and for the default values. This class does not
|
options within a section, and for the default values. When *allow_no_value*
|
||||||
|
is true (default: ``False``), options without values are accepted; the value
|
||||||
|
presented for these is ``None``.
|
||||||
|
|
||||||
|
This class does not
|
||||||
support the magical interpolation behavior.
|
support the magical interpolation behavior.
|
||||||
|
|
||||||
.. versionchanged:: 3.1
|
.. versionchanged:: 3.1
|
||||||
The default *dict_type* is :class:`collections.OrderedDict`.
|
The default *dict_type* is :class:`collections.OrderedDict`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.2
|
||||||
|
*allow_no_value* was added.
|
||||||
|
|
||||||
.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict)
|
|
||||||
|
.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict,
|
||||||
|
allow_no_value=False)
|
||||||
|
|
||||||
Derived class of :class:`RawConfigParser` that implements the magical
|
Derived class of :class:`RawConfigParser` that implements the magical
|
||||||
interpolation feature and adds optional arguments to the :meth:`get` and
|
interpolation feature and adds optional arguments to the :meth:`get` and
|
||||||
|
@ -86,8 +95,12 @@ write-back, as will be the keys within each section.
|
||||||
.. versionchanged:: 3.1
|
.. versionchanged:: 3.1
|
||||||
The default *dict_type* is :class:`collections.OrderedDict`.
|
The default *dict_type* is :class:`collections.OrderedDict`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.2
|
||||||
|
*allow_no_value* was added.
|
||||||
|
|
||||||
.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict)
|
|
||||||
|
.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict,
|
||||||
|
allow_no_value=False)
|
||||||
|
|
||||||
Derived class of :class:`ConfigParser` that implements a more-sane variant of
|
Derived class of :class:`ConfigParser` that implements a more-sane variant of
|
||||||
the magical interpolation feature. This implementation is more predictable as
|
the magical interpolation feature. This implementation is more predictable as
|
||||||
|
@ -99,6 +112,9 @@ write-back, as will be the keys within each section.
|
||||||
.. versionchanged:: 3.1
|
.. versionchanged:: 3.1
|
||||||
The default *dict_type* is :class:`collections.OrderedDict`.
|
The default *dict_type* is :class:`collections.OrderedDict`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.2
|
||||||
|
*allow_no_value* was added.
|
||||||
|
|
||||||
|
|
||||||
.. exception:: NoSectionError
|
.. exception:: NoSectionError
|
||||||
|
|
||||||
|
@ -447,3 +463,38 @@ The function ``opt_move`` below can be used to move options between sections::
|
||||||
opt_move(config, section1, section2, option)
|
opt_move(config, section1, section2, option)
|
||||||
else:
|
else:
|
||||||
config.remove_option(section1, option)
|
config.remove_option(section1, option)
|
||||||
|
|
||||||
|
Some configuration files are known to include settings without values, but which
|
||||||
|
otherwise conform to the syntax supported by :mod:`configparser`. The
|
||||||
|
*allow_no_value* parameter to the constructor can be used to indicate that such
|
||||||
|
values should be accepted:
|
||||||
|
|
||||||
|
.. doctest::
|
||||||
|
|
||||||
|
>>> import configparser
|
||||||
|
>>> import io
|
||||||
|
|
||||||
|
>>> sample_config = """
|
||||||
|
... [mysqld]
|
||||||
|
... user = mysql
|
||||||
|
... pid-file = /var/run/mysqld/mysqld.pid
|
||||||
|
... skip-external-locking
|
||||||
|
... old_passwords = 1
|
||||||
|
... skip-bdb
|
||||||
|
... skip-innodb
|
||||||
|
... """
|
||||||
|
>>> config = configparser.RawConfigParser(allow_no_value=True)
|
||||||
|
>>> config.readfp(io.BytesIO(sample_config))
|
||||||
|
|
||||||
|
>>> # Settings with values are treated as before:
|
||||||
|
>>> config.get("mysqld", "user")
|
||||||
|
'mysql'
|
||||||
|
|
||||||
|
>>> # Settings without values provide None:
|
||||||
|
>>> config.get("mysqld", "skip-bdb")
|
||||||
|
|
||||||
|
>>> # Settings which aren't specified still raise an error:
|
||||||
|
>>> config.get("mysqld", "does-not-exist")
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
configparser.NoOptionError: No option 'does-not-exist' in section: 'mysqld'
|
||||||
|
|
|
@ -221,10 +221,15 @@ class MissingSectionHeaderError(ParsingError):
|
||||||
|
|
||||||
|
|
||||||
class RawConfigParser:
|
class RawConfigParser:
|
||||||
def __init__(self, defaults=None, dict_type=_default_dict):
|
def __init__(self, defaults=None, dict_type=_default_dict,
|
||||||
|
allow_no_value=False):
|
||||||
self._dict = dict_type
|
self._dict = dict_type
|
||||||
self._sections = self._dict()
|
self._sections = self._dict()
|
||||||
self._defaults = self._dict()
|
self._defaults = self._dict()
|
||||||
|
if allow_no_value:
|
||||||
|
self._optcre = self.OPTCRE_NV
|
||||||
|
else:
|
||||||
|
self._optcre = self.OPTCRE
|
||||||
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
|
||||||
|
@ -372,7 +377,7 @@ class RawConfigParser:
|
||||||
return (option in self._sections[section]
|
return (option in self._sections[section]
|
||||||
or option in self._defaults)
|
or option in self._defaults)
|
||||||
|
|
||||||
def set(self, section, option, value):
|
def set(self, section, option, value=None):
|
||||||
"""Set an option."""
|
"""Set an option."""
|
||||||
if not section or section == DEFAULTSECT:
|
if not section or section == DEFAULTSECT:
|
||||||
sectdict = self._defaults
|
sectdict = self._defaults
|
||||||
|
@ -394,8 +399,11 @@ class RawConfigParser:
|
||||||
fp.write("[%s]\n" % section)
|
fp.write("[%s]\n" % section)
|
||||||
for (key, value) in self._sections[section].items():
|
for (key, value) in self._sections[section].items():
|
||||||
if key != "__name__":
|
if key != "__name__":
|
||||||
fp.write("%s = %s\n" %
|
if value is None:
|
||||||
(key, str(value).replace('\n', '\n\t')))
|
fp.write("%s\n" % (key))
|
||||||
|
else:
|
||||||
|
fp.write("%s = %s\n" %
|
||||||
|
(key, str(value).replace('\n', '\n\t')))
|
||||||
fp.write("\n")
|
fp.write("\n")
|
||||||
|
|
||||||
def remove_option(self, section, option):
|
def remove_option(self, section, option):
|
||||||
|
@ -436,6 +444,15 @@ class RawConfigParser:
|
||||||
# by any # space/tab
|
# by any # space/tab
|
||||||
r'(?P<value>.*)$' # everything up to eol
|
r'(?P<value>.*)$' # everything up to eol
|
||||||
)
|
)
|
||||||
|
OPTCRE_NV = re.compile(
|
||||||
|
r'(?P<option>[^:=\s][^:=]*)' # very permissive!
|
||||||
|
r'\s*(?:' # any number of space/tab,
|
||||||
|
r'(?P<vi>[:=])\s*' # optionally followed by
|
||||||
|
# separator (either : or
|
||||||
|
# =), followed by any #
|
||||||
|
# space/tab
|
||||||
|
r'(?P<value>.*))?$' # everything up to eol
|
||||||
|
)
|
||||||
|
|
||||||
def _read(self, fp, fpname):
|
def _read(self, fp, fpname):
|
||||||
"""Parse a sectioned setup file.
|
"""Parse a sectioned setup file.
|
||||||
|
@ -488,16 +505,19 @@ class RawConfigParser:
|
||||||
raise MissingSectionHeaderError(fpname, lineno, line)
|
raise MissingSectionHeaderError(fpname, lineno, line)
|
||||||
# an option line?
|
# an option line?
|
||||||
else:
|
else:
|
||||||
mo = self.OPTCRE.match(line)
|
mo = self._optcre.match(line)
|
||||||
if mo:
|
if mo:
|
||||||
optname, vi, optval = mo.group('option', 'vi', 'value')
|
optname, vi, optval = mo.group('option', 'vi', 'value')
|
||||||
if vi in ('=', ':') and ';' in optval:
|
# This check is fine because the OPTCRE cannot
|
||||||
# ';' is a comment delimiter only if it follows
|
# match if it would set optval to None
|
||||||
# a spacing character
|
if optval is not None:
|
||||||
pos = optval.find(';')
|
if vi in ('=', ':') and ';' in optval:
|
||||||
if pos != -1 and optval[pos-1].isspace():
|
# ';' is a comment delimiter only if it follows
|
||||||
optval = optval[:pos]
|
# a spacing character
|
||||||
optval = optval.strip()
|
pos = optval.find(';')
|
||||||
|
if pos != -1 and optval[pos-1].isspace():
|
||||||
|
optval = optval[:pos]
|
||||||
|
optval = optval.strip()
|
||||||
# allow empty values
|
# allow empty values
|
||||||
if optval == '""':
|
if optval == '""':
|
||||||
optval = ''
|
optval = ''
|
||||||
|
@ -545,7 +565,7 @@ class ConfigParser(RawConfigParser):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NoOptionError(option, section)
|
raise NoOptionError(option, section)
|
||||||
|
|
||||||
if raw:
|
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)
|
||||||
|
@ -588,7 +608,7 @@ class ConfigParser(RawConfigParser):
|
||||||
depth = MAX_INTERPOLATION_DEPTH
|
depth = MAX_INTERPOLATION_DEPTH
|
||||||
while depth: # Loop through this until it's done
|
while depth: # Loop through this until it's done
|
||||||
depth -= 1
|
depth -= 1
|
||||||
if "%(" in value:
|
if value and "%(" in value:
|
||||||
value = self._KEYCRE.sub(self._interpolation_replace, value)
|
value = self._KEYCRE.sub(self._interpolation_replace, value)
|
||||||
try:
|
try:
|
||||||
value = value % vars
|
value = value % vars
|
||||||
|
@ -597,7 +617,7 @@ class ConfigParser(RawConfigParser):
|
||||||
option, section, rawval, e.args[0])
|
option, section, rawval, e.args[0])
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
if "%(" in value:
|
if value and "%(" in value:
|
||||||
raise InterpolationDepthError(option, section, rawval)
|
raise InterpolationDepthError(option, section, rawval)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -659,10 +679,16 @@ class SafeConfigParser(ConfigParser):
|
||||||
option, section,
|
option, section,
|
||||||
"'%%' must be followed by '%%' or '(', found: %r" % (rest,))
|
"'%%' must be followed by '%%' or '(', found: %r" % (rest,))
|
||||||
|
|
||||||
def set(self, section, option, value):
|
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."""
|
||||||
if not isinstance(value, str):
|
# The only legal non-string value if we allow valueless
|
||||||
raise TypeError("option values must be strings")
|
# 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:
|
||||||
# first, replace all "good" interpolations
|
# first, replace all "good" interpolations
|
||||||
tmp_value = value.replace('%%', '')
|
tmp_value = value.replace('%%', '')
|
||||||
|
|
|
@ -6,6 +6,7 @@ import collections
|
||||||
from test import support
|
from test import support
|
||||||
|
|
||||||
class SortedDict(collections.UserDict):
|
class SortedDict(collections.UserDict):
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return sorted(self.data.items())
|
return sorted(self.data.items())
|
||||||
|
|
||||||
|
@ -20,12 +21,16 @@ class SortedDict(collections.UserDict):
|
||||||
__iter__ = iterkeys
|
__iter__ = iterkeys
|
||||||
def itervalues(self): return iter(self.values())
|
def itervalues(self): return iter(self.values())
|
||||||
|
|
||||||
|
|
||||||
class TestCaseBase(unittest.TestCase):
|
class TestCaseBase(unittest.TestCase):
|
||||||
|
allow_no_value = False
|
||||||
|
|
||||||
def newconfig(self, defaults=None):
|
def newconfig(self, defaults=None):
|
||||||
if defaults is None:
|
if defaults is None:
|
||||||
self.cf = self.config_class()
|
self.cf = self.config_class(allow_no_value=self.allow_no_value)
|
||||||
else:
|
else:
|
||||||
self.cf = self.config_class(defaults)
|
self.cf = self.config_class(defaults,
|
||||||
|
allow_no_value=self.allow_no_value)
|
||||||
return self.cf
|
return self.cf
|
||||||
|
|
||||||
def fromstring(self, string, defaults=None):
|
def fromstring(self, string, defaults=None):
|
||||||
|
@ -35,7 +40,7 @@ class TestCaseBase(unittest.TestCase):
|
||||||
return cf
|
return cf
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
cf = self.fromstring(
|
config_string = (
|
||||||
"[Foo Bar]\n"
|
"[Foo Bar]\n"
|
||||||
"foo=bar\n"
|
"foo=bar\n"
|
||||||
"[Spacey Bar]\n"
|
"[Spacey Bar]\n"
|
||||||
|
@ -55,17 +60,28 @@ class TestCaseBase(unittest.TestCase):
|
||||||
"key with spaces : value\n"
|
"key with spaces : value\n"
|
||||||
"another with spaces = splat!\n"
|
"another with spaces = splat!\n"
|
||||||
)
|
)
|
||||||
|
if self.allow_no_value:
|
||||||
|
config_string += (
|
||||||
|
"[NoValue]\n"
|
||||||
|
"option-without-value\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
cf = self.fromstring(config_string)
|
||||||
L = cf.sections()
|
L = cf.sections()
|
||||||
L.sort()
|
L.sort()
|
||||||
|
E = [r'Commented Bar',
|
||||||
|
r'Foo Bar',
|
||||||
|
r'Internationalized Stuff',
|
||||||
|
r'Long Line',
|
||||||
|
r'Section\with$weird%characters[' '\t',
|
||||||
|
r'Spaces',
|
||||||
|
r'Spacey Bar',
|
||||||
|
]
|
||||||
|
if self.allow_no_value:
|
||||||
|
E.append(r'NoValue')
|
||||||
|
E.sort()
|
||||||
eq = self.assertEqual
|
eq = self.assertEqual
|
||||||
eq(L, [r'Commented Bar',
|
eq(L, E)
|
||||||
r'Foo Bar',
|
|
||||||
r'Internationalized Stuff',
|
|
||||||
r'Long Line',
|
|
||||||
r'Section\with$weird%characters[' '\t',
|
|
||||||
r'Spaces',
|
|
||||||
r'Spacey Bar',
|
|
||||||
])
|
|
||||||
|
|
||||||
# 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:
|
||||||
|
@ -75,6 +91,8 @@ class TestCaseBase(unittest.TestCase):
|
||||||
eq(cf.get('Commented Bar', 'foo'), 'bar')
|
eq(cf.get('Commented Bar', 'foo'), 'bar')
|
||||||
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!')
|
||||||
|
if self.allow_no_value:
|
||||||
|
eq(cf.get('NoValue', 'option-without-value'), None)
|
||||||
|
|
||||||
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!')
|
||||||
|
@ -146,8 +164,6 @@ class TestCaseBase(unittest.TestCase):
|
||||||
"[Foo]\n extra-spaces: splat\n")
|
"[Foo]\n extra-spaces: splat\n")
|
||||||
self.parse_error(configparser.ParsingError,
|
self.parse_error(configparser.ParsingError,
|
||||||
"[Foo]\n extra-spaces= splat\n")
|
"[Foo]\n extra-spaces= splat\n")
|
||||||
self.parse_error(configparser.ParsingError,
|
|
||||||
"[Foo]\noption-without-value\n")
|
|
||||||
self.parse_error(configparser.ParsingError,
|
self.parse_error(configparser.ParsingError,
|
||||||
"[Foo]\n:value-without-option-name\n")
|
"[Foo]\n:value-without-option-name\n")
|
||||||
self.parse_error(configparser.ParsingError,
|
self.parse_error(configparser.ParsingError,
|
||||||
|
@ -214,18 +230,24 @@ class TestCaseBase(unittest.TestCase):
|
||||||
cf.add_section, "Foo")
|
cf.add_section, "Foo")
|
||||||
|
|
||||||
def test_write(self):
|
def test_write(self):
|
||||||
cf = self.fromstring(
|
config_string = (
|
||||||
"[Long Line]\n"
|
"[Long Line]\n"
|
||||||
"foo: this line is much, much longer than my editor\n"
|
"foo: this line is much, much longer than my editor\n"
|
||||||
" likes it.\n"
|
" likes it.\n"
|
||||||
"[DEFAULT]\n"
|
"[DEFAULT]\n"
|
||||||
"foo: another very\n"
|
"foo: another very\n"
|
||||||
" long line"
|
" long line\n"
|
||||||
)
|
)
|
||||||
|
if self.allow_no_value:
|
||||||
|
config_string += (
|
||||||
|
"[Valueless]\n"
|
||||||
|
"option-without-value\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
cf = self.fromstring(config_string)
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
cf.write(output)
|
cf.write(output)
|
||||||
self.assertEqual(
|
expect_string = (
|
||||||
output.getvalue(),
|
|
||||||
"[DEFAULT]\n"
|
"[DEFAULT]\n"
|
||||||
"foo = another very\n"
|
"foo = another very\n"
|
||||||
"\tlong line\n"
|
"\tlong line\n"
|
||||||
|
@ -235,6 +257,13 @@ class TestCaseBase(unittest.TestCase):
|
||||||
"\tlikes it.\n"
|
"\tlikes it.\n"
|
||||||
"\n"
|
"\n"
|
||||||
)
|
)
|
||||||
|
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):
|
def test_set_string_types(self):
|
||||||
cf = self.fromstring("[sect]\n"
|
cf = self.fromstring("[sect]\n"
|
||||||
|
@ -328,7 +357,7 @@ class ConfigParserTestCase(TestCaseBase):
|
||||||
self.get_error(configparser.InterpolationDepthError, "Foo", "bar11")
|
self.get_error(configparser.InterpolationDepthError, "Foo", "bar11")
|
||||||
|
|
||||||
def test_interpolation_missing_value(self):
|
def test_interpolation_missing_value(self):
|
||||||
cf = self.get_interpolation_config()
|
self.get_interpolation_config()
|
||||||
e = self.get_error(configparser.InterpolationError,
|
e = self.get_error(configparser.InterpolationError,
|
||||||
"Interpolation Error", "name")
|
"Interpolation Error", "name")
|
||||||
self.assertEqual(e.reference, "reference")
|
self.assertEqual(e.reference, "reference")
|
||||||
|
@ -448,6 +477,11 @@ class SafeConfigParserTestCase(ConfigParserTestCase):
|
||||||
cf = self.newconfig()
|
cf = self.newconfig()
|
||||||
self.assertRaises(ValueError, cf.add_section, "DEFAULT")
|
self.assertRaises(ValueError, cf.add_section, "DEFAULT")
|
||||||
|
|
||||||
|
|
||||||
|
class SafeConfigParserTestCaseNoValue(SafeConfigParserTestCase):
|
||||||
|
allow_no_value = True
|
||||||
|
|
||||||
|
|
||||||
class SortedTestCase(RawConfigParserTestCase):
|
class SortedTestCase(RawConfigParserTestCase):
|
||||||
def newconfig(self, defaults=None):
|
def newconfig(self, defaults=None):
|
||||||
self.cf = self.config_class(defaults=defaults, dict_type=SortedDict)
|
self.cf = self.config_class(defaults=defaults, dict_type=SortedDict)
|
||||||
|
@ -472,13 +506,16 @@ class SortedTestCase(RawConfigParserTestCase):
|
||||||
"o3 = 2\n"
|
"o3 = 2\n"
|
||||||
"o4 = 1\n\n")
|
"o4 = 1\n\n")
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
support.run_unittest(
|
support.run_unittest(
|
||||||
ConfigParserTestCase,
|
ConfigParserTestCase,
|
||||||
RawConfigParserTestCase,
|
RawConfigParserTestCase,
|
||||||
SafeConfigParserTestCase,
|
SafeConfigParserTestCase,
|
||||||
SortedTestCase
|
SortedTestCase,
|
||||||
)
|
SafeConfigParserTestCaseNoValue,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main()
|
test_main()
|
||||||
|
|
Loading…
Reference in New Issue