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:
parent
d2a9b20efa
commit
2cf9ddb390
|
@ -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;
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue