configparser: fixed inconsistency where in SafeConfigParser option values

were ensured to be strings but section names and option keys were not.
 Behaviour unchanged for RawConfigParser and ConfigParser.
This commit is contained in:
Łukasz Langa 2010-12-04 12:46:01 +00:00
parent d2a9b20efa
commit 2cf9ddb390
3 changed files with 60 additions and 8 deletions

View File

@ -836,7 +836,11 @@ SafeConfigParser Objects
Add a section named *section* to the instance. If a section by the given
name already exists, :exc:`DuplicateSectionError` is raised. If the
*default section* name is passed, :exc:`ValueError` is raised.
*default section* name is passed, :exc:`ValueError` is raised. The name
of the section must be a string; if not, :exc:`TypeError` is raised.
.. versionchanged:: 3.2
Non-string section names raise :exc:`TypeError`.
.. method:: has_section(section)
@ -976,8 +980,8 @@ SafeConfigParser Objects
.. method:: set(section, option, value)
If the given section exists, set the given option to the specified value;
otherwise raise :exc:`NoSectionError`. *value* must be a string; if not,
:exc:`TypeError` is raised.
otherwise raise :exc:`NoSectionError`. *option* and *value* must be
strings; if not, :exc:`TypeError` is raised.
.. method:: write(fileobject, space_around_delimiters=True)
@ -1044,7 +1048,7 @@ RawConfigParser Objects
.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True, default_section=configaparser.DEFAULTSECT, interpolation=None)
Legacy variant of the :class:`SafeConfigParser` with interpolation disabled
by default and an unsafe ``set`` method.
by default and unsafe ``add_section`` and ``set`` methods.
.. note::
Consider using :class:`SafeConfigParser` instead which checks types of
@ -1052,6 +1056,16 @@ RawConfigParser Objects
can use ``SafeConfigParser(interpolation=None)``.
.. method:: add_section(section)
Add a section named *section* to the instance. If a section by the given
name already exists, :exc:`DuplicateSectionError` is raised. If the
*default section* name is passed, :exc:`ValueError` is raised.
Type of *section* is not checked which lets users create non-string named
sections. This behaviour is unsupported and may cause internal errors.
.. method:: set(section, option, value)
If the given section exists, set the given option to the specified value;

View File

@ -727,11 +727,15 @@ class RawConfigParser(MutableMapping):
that should be present in the section. If the used dictionary type
preserves order, sections and their keys will be added in order.
All types held in the dictionary are converted to strings during
reading, including section names, option names and keys.
Optional second argument is the `source' specifying the name of the
dictionary being read.
"""
elements_added = set()
for section, keys in dictionary.items():
section = str(section)
try:
self.add_section(section)
except (DuplicateSectionError, ValueError):
@ -739,7 +743,7 @@ class RawConfigParser(MutableMapping):
raise
elements_added.add(section)
for key, value in keys.items():
key = self.optionxform(key)
key = self.optionxform(str(key))
if value is not None:
value = str(value)
if self._strict and (section, key) in elements_added:
@ -1128,7 +1132,7 @@ class RawConfigParser(MutableMapping):
raise ValueError('Not a boolean: %s' % value)
return self.BOOLEAN_STATES[value.lower()]
def _validate_value_type(self, value):
def _validate_value_types(self, *, section="", option="", value=""):
"""Raises a TypeError for non-string values.
The only legal non-string value if we allow valueless
@ -1141,6 +1145,10 @@ class RawConfigParser(MutableMapping):
for RawConfigParsers and ConfigParsers. It is invoked in every
case for mapping protocol access and in SafeConfigParser.set().
"""
if not isinstance(section, str):
raise TypeError("section names must be strings")
if not isinstance(option, str):
raise TypeError("option keys must be strings")
if not self._allow_no_value or value:
if not isinstance(value, str):
raise TypeError("option values must be strings")
@ -1169,9 +1177,16 @@ class SafeConfigParser(ConfigParser):
def set(self, section, option, value=None):
"""Set an option. Extends RawConfigParser.set by validating type and
interpolation syntax on the value."""
self._validate_value_type(value)
self._validate_value_types(option=option, value=value)
super().set(section, option, value)
def add_section(self, section):
"""Create a new section in the configuration. Extends
RawConfigParser.add_section by validating if the section name is
a string."""
self._validate_value_types(section=section)
super().add_section(section)
class SectionProxy(MutableMapping):
"""A proxy for a single section from a parser."""
@ -1196,7 +1211,7 @@ class SectionProxy(MutableMapping):
return self._parser.get(self._name, key)
def __setitem__(self, key, value):
self._parser._validate_value_type(value)
self._parser._validate_value_types(option=key, value=value)
return self._parser.set(self._name, key, value)
def __delitem__(self, key):

View File

@ -106,6 +106,7 @@ class BasicTestCase(CfgParserTestCaseClass):
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')
if self.allow_no_value:
eq(cf.get('NoValue', 'option-without-value'), None)
@ -214,6 +215,7 @@ another with spaces {0[0]} splat!
int {0[1]} 42
float {0[0]} 0.44
boolean {0[0]} NO
123 {0[1]} strange but acceptable
""".format(self.delimiters, self.comment_prefixes)
if self.allow_no_value:
config_string += (
@ -286,6 +288,7 @@ boolean {0[0]} NO
"int": 42,
"float": 0.44,
"boolean": False,
123: "strange but acceptable",
},
}
if self.allow_no_value:
@ -716,6 +719,15 @@ class ConfigParserTestCase(BasicTestCase):
raw=True), '%(list)s')
self.assertRaises(ValueError, cf.get, 'non-string',
'string_with_interpolation', raw=False)
cf.add_section(123)
cf.set(123, 'this is sick', True)
self.assertEqual(cf.get(123, 'this is sick', raw=True), True)
with self.assertRaises(TypeError):
cf.get(123, 'this is sick')
cf.optionxform = lambda x: x
cf.set('non-string', 1, 1)
self.assertRaises(TypeError, cf.get, 'non-string', 1, 1)
self.assertEqual(cf.get('non-string', 1, raw=True), 1)
class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase):
delimiters = (':=', '$')
@ -783,6 +795,15 @@ class RawConfigParserTestCase(BasicTestCase):
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.__class__ is configparser._default_dict:
# would not work for SortedDict; only checking for the most common
# default dictionary (OrderedDict)
cf.optionxform = lambda x: x
cf.set('non-string', 1, 1)
self.assertEqual(cf.get('non-string', 1), 1)
class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
delimiters = (':=', '$')
@ -848,6 +869,8 @@ class SafeConfigParserTestCase(ConfigParserTestCase):
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()