Kaydet (Commit) 2cf9ddb3 authored tarafından Łukasz Langa's avatar Łukasz Langa

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.
üst d2a9b20e
...@@ -836,7 +836,11 @@ SafeConfigParser Objects ...@@ -836,7 +836,11 @@ SafeConfigParser Objects
Add a section named *section* to the instance. If a section by the given Add a section named *section* to the instance. If a section by the given
name already exists, :exc:`DuplicateSectionError` is raised. If the 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) .. method:: has_section(section)
...@@ -976,8 +980,8 @@ SafeConfigParser Objects ...@@ -976,8 +980,8 @@ SafeConfigParser Objects
.. method:: set(section, option, value) .. method:: set(section, option, value)
If the given section exists, set the given option to the specified 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, otherwise raise :exc:`NoSectionError`. *option* and *value* must be
:exc:`TypeError` is raised. strings; if not, :exc:`TypeError` is raised.
.. method:: write(fileobject, space_around_delimiters=True) .. method:: write(fileobject, space_around_delimiters=True)
...@@ -1044,7 +1048,7 @@ RawConfigParser Objects ...@@ -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) .. 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 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:: .. note::
Consider using :class:`SafeConfigParser` instead which checks types of Consider using :class:`SafeConfigParser` instead which checks types of
...@@ -1052,6 +1056,16 @@ RawConfigParser Objects ...@@ -1052,6 +1056,16 @@ RawConfigParser Objects
can use ``SafeConfigParser(interpolation=None)``. 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) .. method:: set(section, option, value)
If the given section exists, set the given option to the specified value; If the given section exists, set the given option to the specified value;
......
...@@ -727,11 +727,15 @@ class RawConfigParser(MutableMapping): ...@@ -727,11 +727,15 @@ class RawConfigParser(MutableMapping):
that should be present in the section. If the used dictionary type that should be present in the section. If the used dictionary type
preserves order, sections and their keys will be added in order. 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 Optional second argument is the `source' specifying the name of the
dictionary being read. dictionary being read.
""" """
elements_added = set() elements_added = set()
for section, keys in dictionary.items(): for section, keys in dictionary.items():
section = str(section)
try: try:
self.add_section(section) self.add_section(section)
except (DuplicateSectionError, ValueError): except (DuplicateSectionError, ValueError):
...@@ -739,7 +743,7 @@ class RawConfigParser(MutableMapping): ...@@ -739,7 +743,7 @@ class RawConfigParser(MutableMapping):
raise raise
elements_added.add(section) elements_added.add(section)
for key, value in keys.items(): for key, value in keys.items():
key = self.optionxform(key) key = self.optionxform(str(key))
if value is not None: if value is not None:
value = str(value) value = str(value)
if self._strict and (section, key) in elements_added: if self._strict and (section, key) in elements_added:
...@@ -1128,7 +1132,7 @@ class RawConfigParser(MutableMapping): ...@@ -1128,7 +1132,7 @@ class RawConfigParser(MutableMapping):
raise ValueError('Not a boolean: %s' % value) raise ValueError('Not a boolean: %s' % value)
return self.BOOLEAN_STATES[value.lower()] 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. """Raises a TypeError for non-string values.
The only legal non-string value if we allow valueless The only legal non-string value if we allow valueless
...@@ -1141,6 +1145,10 @@ class RawConfigParser(MutableMapping): ...@@ -1141,6 +1145,10 @@ class RawConfigParser(MutableMapping):
for RawConfigParsers and ConfigParsers. It is invoked in every for RawConfigParsers and ConfigParsers. It is invoked in every
case for mapping protocol access and in SafeConfigParser.set(). 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 self._allow_no_value or value:
if not isinstance(value, str): if not isinstance(value, str):
raise TypeError("option values must be strings") raise TypeError("option values must be strings")
...@@ -1169,9 +1177,16 @@ class SafeConfigParser(ConfigParser): ...@@ -1169,9 +1177,16 @@ class SafeConfigParser(ConfigParser):
def set(self, section, option, value=None): def set(self, section, option, value=None):
"""Set an option. Extends RawConfigParser.set by validating type and """Set an option. Extends RawConfigParser.set by validating type and
interpolation syntax on the value.""" interpolation syntax on the value."""
self._validate_value_type(value) self._validate_value_types(option=option, value=value)
super().set(section, option, 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): class SectionProxy(MutableMapping):
"""A proxy for a single section from a parser.""" """A proxy for a single section from a parser."""
...@@ -1196,7 +1211,7 @@ class SectionProxy(MutableMapping): ...@@ -1196,7 +1211,7 @@ class SectionProxy(MutableMapping):
return self._parser.get(self._name, key) return self._parser.get(self._name, key)
def __setitem__(self, key, value): 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) return self._parser.set(self._name, key, value)
def __delitem__(self, key): def __delitem__(self, key):
......
...@@ -106,6 +106,7 @@ class BasicTestCase(CfgParserTestCaseClass): ...@@ -106,6 +106,7 @@ class BasicTestCase(CfgParserTestCaseClass):
self.assertAlmostEqual(cf.getfloat('Types', 'float'), 0.44) self.assertAlmostEqual(cf.getfloat('Types', 'float'), 0.44)
eq(cf.get('Types', 'float'), "0.44") eq(cf.get('Types', 'float'), "0.44")
eq(cf.getboolean('Types', 'boolean'), False) eq(cf.getboolean('Types', 'boolean'), False)
eq(cf.get('Types', '123'), 'strange but acceptable')
if self.allow_no_value: if self.allow_no_value:
eq(cf.get('NoValue', 'option-without-value'), None) eq(cf.get('NoValue', 'option-without-value'), None)
...@@ -214,6 +215,7 @@ another with spaces {0[0]} splat! ...@@ -214,6 +215,7 @@ another with spaces {0[0]} splat!
int {0[1]} 42 int {0[1]} 42
float {0[0]} 0.44 float {0[0]} 0.44
boolean {0[0]} NO boolean {0[0]} NO
123 {0[1]} strange but acceptable
""".format(self.delimiters, self.comment_prefixes) """.format(self.delimiters, self.comment_prefixes)
if self.allow_no_value: if self.allow_no_value:
config_string += ( config_string += (
...@@ -286,6 +288,7 @@ boolean {0[0]} NO ...@@ -286,6 +288,7 @@ boolean {0[0]} NO
"int": 42, "int": 42,
"float": 0.44, "float": 0.44,
"boolean": False, "boolean": False,
123: "strange but acceptable",
}, },
} }
if self.allow_no_value: if self.allow_no_value:
...@@ -716,6 +719,15 @@ class ConfigParserTestCase(BasicTestCase): ...@@ -716,6 +719,15 @@ class ConfigParserTestCase(BasicTestCase):
raw=True), '%(list)s') raw=True), '%(list)s')
self.assertRaises(ValueError, cf.get, 'non-string', self.assertRaises(ValueError, cf.get, 'non-string',
'string_with_interpolation', raw=False) '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): class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase):
delimiters = (':=', '$') delimiters = (':=', '$')
...@@ -783,6 +795,15 @@ class RawConfigParserTestCase(BasicTestCase): ...@@ -783,6 +795,15 @@ class RawConfigParserTestCase(BasicTestCase):
self.assertEqual(cf.get('non-string', 'list'), self.assertEqual(cf.get('non-string', 'list'),
[0, 1, 1, 2, 3, 5, 8, 13]) [0, 1, 1, 2, 3, 5, 8, 13])
self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159}) 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): class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
delimiters = (':=', '$') delimiters = (':=', '$')
...@@ -848,6 +869,8 @@ class SafeConfigParserTestCase(ConfigParserTestCase): ...@@ -848,6 +869,8 @@ class SafeConfigParserTestCase(ConfigParserTestCase):
self.assertRaises(TypeError, cf.set, "sect", "option2", 1) self.assertRaises(TypeError, cf.set, "sect", "option2", 1)
self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0) self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0)
self.assertRaises(TypeError, cf.set, "sect", "option2", object()) self.assertRaises(TypeError, cf.set, "sect", "option2", object())
self.assertRaises(TypeError, cf.set, "sect", 123, "invalid opt name!")
self.assertRaises(TypeError, cf.add_section, 123)
def test_add_section_default(self): def test_add_section_default(self):
cf = self.newconfig() cf = self.newconfig()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment