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:
Fred Drake 2010-02-19 06:08:41 +00:00
parent 2c2dc37e58
commit 03c44a30a3
3 changed files with 156 additions and 42 deletions

View File

@ -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'

View File

@ -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('%%', '')

View File

@ -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()