diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index 6822176093d..154d0625901 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -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; diff --git a/Lib/configparser.py b/Lib/configparser.py index eafcea3382f..e92d7fa3f5e 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -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): diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py index 1ae720ecbb0..24158ad104b 100644 --- a/Lib/test/test_cfgparser.py +++ b/Lib/test/test_cfgparser.py @@ -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()