mirror of https://github.com/python/cpython.git
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 @@ def read_dict(self, dictionary, source='<dict>'):
|
|||
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 @@ def read_dict(self, dictionary, source='<dict>'):
|
|||
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 @@ def _convert_to_boolean(self, value):
|
|||
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 @@ def _validate_value_type(self, value):
|
|||
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 @@ def __getitem__(self, key):
|
|||
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 @@ def basic_test(self, cf):
|
|||
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 @@ def test_basic(self):
|
|||
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 @@ def test_basic_from_dict(self):
|
|||
"int": 42,
|
||||
"float": 0.44,
|
||||
"boolean": False,
|
||||
123: "strange but acceptable",
|
||||
},
|
||||
}
|
||||
if self.allow_no_value:
|
||||
|
@ -716,6 +719,15 @@ def test_set_nonstring_types(self):
|
|||
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 @@ def test_set_nonstring_types(self):
|
|||
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 @@ def test_set_nonstring_types(self):
|
|||
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