Index: Objects/dictobject.c =================================================================== --- Objects/dictobject.c (revision 83688) +++ Objects/dictobject.c (working copy) @@ -10,15 +10,16 @@ #include "Python.h" #include "stringlib/eq.h" +static PyObject *keyerror_message = NULL; -/* Set a key error with the specified argument, wrapping it in a +/* Set a key error with the specified arguments, wrapping it in a * tuple automatically so that tuple keys are not unpacked as the * exception arguments. */ static void set_key_error(PyObject *arg) { PyObject *tup; - tup = PyTuple_Pack(1, arg); + tup = PyTuple_Pack(2, keyerror_message, arg); if (!tup) return; /* caller will expect error to be set anyway */ PyErr_SetObject(PyExc_KeyError, tup); @@ -246,6 +247,9 @@ dummy = PyUnicode_FromString(""); if (dummy == NULL) return NULL; + keyerror_message = PyUnicode_FromString("Not in dict"); + if (keyerror_message == NULL) + return NULL; #ifdef SHOW_CONVERSION_COUNTS Py_AtExit(show_counts); #endif Index: Objects/exceptions.c =================================================================== --- Objects/exceptions.c (revision 83688) +++ Objects/exceptions.c (working copy) @@ -1082,17 +1082,24 @@ static PyObject * KeyError_str(PyBaseExceptionObject *self) { - /* If args is a tuple of exactly one item, apply repr to args[0]. + /* If args is a tuple of exactly two items, the first is the message, and + the second is the offending key, with %r formatting applied. This is done so that e.g. the exception raised by {}[''] prints KeyError: '' rather than the confusing KeyError - alone. The downside is that if KeyError is raised with an explanatory - string, that string will be displayed in quotes. Too bad. + alone. If args is anything else, use the default BaseException__str__(). */ - if (PyTuple_GET_SIZE(self->args) == 1) { - return PyObject_Repr(PyTuple_GET_ITEM(self->args, 0)); + if (PyTuple_GET_SIZE(self->args) == 2) { + PyObject *fmt, *ret; + fmt = PyUnicode_FromString("%s: %r"); + if (!fmt) + return NULL; + + ret = PyUnicode_Format(fmt, self->args); + Py_DECREF(fmt); + return ret; } return BaseException_str(self); } Index: Objects/setobject.c =================================================================== --- Objects/setobject.c (revision 83688) +++ Objects/setobject.c (working copy) @@ -18,7 +18,14 @@ set_key_error(PyObject *arg) { PyObject *tup; - tup = PyTuple_Pack(1, arg); + static PyObject *keyerror_message = NULL; + if (!keyerror_message) + { + keyerror_message = PyUnicode_FromString("Not in set"); + if (!keyerror_message) + return; + } + tup = PyTuple_Pack(2, keyerror_message, arg); if (!tup) return; /* caller will expect error to be set anyway */ PyErr_SetObject(PyExc_KeyError, tup); Index: Doc/library/configparser.rst =================================================================== --- Doc/library/configparser.rst (revision 83688) +++ Doc/library/configparser.rst (working copy) @@ -27,8 +27,9 @@ the Windows Registry extended version of INI syntax. A configuration file consists of sections, each led by a ``[section]`` header, -followed by name/value entries separated by a specific string (``=`` or ``:`` by -default). Note that leading whitespace is removed from values. Values can be +followed by key/value entries separated by a specific string (``=`` or ``:`` by +default). By default, section names are case sensitive but keys are not. Leading +and trailing whitespace is removed from keys and from values. Values can be ommitted, in which case the key/value delimiter may also be left out. Values can also span multiple lines, as long as they are indented deeper than the first line of the value. Depending on the parser's mode, blank lines may be treated @@ -101,7 +102,7 @@ keys within each section. -.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, empty_lines_in_values=True, allow_no_value=False) +.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True, allow_no_value=False) The basic configuration object. When *defaults* is given, it is initialized into the dictionary of intrinsic defaults. When *dict_type* is given, it @@ -115,11 +116,14 @@ *comment_prefixes* is a special value that indicates that ``;`` and ``#`` can start whole line comments while only ``;`` can start inline comments. - When *empty_lines_in_values* is ``False`` (default: ``True``), each empty - line marks the end of an option. Otherwise, internal empty lines of a - multiline option are kept as part of the value. When *allow_no_value* is - true (default: ``False``), options without values are accepted; the value - presented for these is ``None``. + When *strict* is ``True`` (default: ``False``), the parser won't allow for + any section or option duplicates while reading from a single source (file, + string or dictionary), raising :exc:`DuplicateSectionError` or + :exc:`DuplicateOptionError`. When *empty_lines_in_values* is ``False`` + (default: ``True``), each empty line marks the end of an option. Otherwise, + internal empty lines of a multiline option are kept as part of the value. + When *allow_no_value* is true (default: ``False``), options without values + are accepted; the value presented for these is ``None``. This class does not support the magical interpolation behavior. @@ -127,11 +131,11 @@ The default *dict_type* is :class:`collections.OrderedDict`. .. versionchanged:: 3.2 - *delimiters*, *comment_prefixes*, *empty_lines_in_values* and + *delimiters*, *comment_prefixes*, *strict*, *empty_lines_in_values* and *allow_no_value* were added. -.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=('#', ';'), empty_lines_in_values=True, allow_no_value=False) +.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=('#', ';'), strict=False, empty_lines_in_values=True, allow_no_value=False) Derived class of :class:`ConfigParser` that implements a sane variant of the magical interpolation feature. This implementation is more predictable as it @@ -151,7 +155,7 @@ *allow_no_value* were added. -.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=('#', ';'), empty_lines_in_values=True, allow_no_value=False) +.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=('#', ';'), strict=False, empty_lines_in_values=True, allow_no_value=False) Derived class of :class:`RawConfigParser` that implements the magical interpolation feature and adds optional arguments to the :meth:`get` and @@ -194,6 +198,14 @@ that is already present. +.. exception:: DuplicateOptionError + + Exception raised if a single option appears twice during reading from + a single file, string or dictionary. This catches mispellings and + case-sensitivity related errors (a dictionary may have two keys representing + the same case-insensitive configuration key). + + .. exception:: NoOptionError Exception raised when a specified option is not found in the specified section. @@ -315,16 +327,34 @@ default encoding for :func:`open`. -.. method:: RawConfigParser.readfp(fp, filename=None) +.. method:: RawConfigParser.read_file(f, filename=None) - Read and parse configuration data from the file or file-like object in *fp* + Read and parse configuration data from the file or file-like object in *f* (only the :meth:`readline` method is used). The file-like object must operate in text mode, i.e. return strings from :meth:`readline`. - If *filename* is omitted and *fp* has a :attr:`name` attribute, that is used + If *filename* is omitted and *f* has a :attr:`name` attribute, that is used for *filename*; the default is ````. + .. versionadded:: 3.2 + Renamed from :meth:`readfp`. + +.. method:: RawConfigParser.read_string(string) + + Parse configuration data from a given string. + + .. versionadded:: 3.2 + +.. method:: RawConfigParser.read_dict(dictionary) + + Parse configuration from a dictionary. Keys are section names, values are + dictionaries with keys and values that should be present in the section. If + the used dictionary type preserves order, sections and their keys will be + added in order. + + .. versionadded:: 3.2 + .. method:: RawConfigParser.get(section, option) Get an *option* value for the named *section*. @@ -408,7 +438,11 @@ Note that when reading configuration files, whitespace around the option names are stripped before :meth:`optionxform` is called. +.. method:: RawConfigParser.readfp(fp, filename=None) + .. deprecated:: 3.2 + Please use :meth:`read_file` instead. + .. _configparser-objects: ConfigParser Objects Index: Lib/configparser.py =================================================================== --- Lib/configparser.py (revision 83688) +++ Lib/configparser.py (working copy) @@ -735,7 +735,7 @@ value = value % vars except KeyError as e: raise InterpolationMissingOptionError( - option, section, rawval, e.args[0]) + option, section, rawval, e.args[-1]) else: break if value and "%(" in value: Index: Lib/_abcoll.py =================================================================== --- Lib/_abcoll.py (revision 83688) +++ Lib/_abcoll.py (working copy) @@ -289,7 +289,7 @@ def remove(self, value): """Remove an element. If not a member, raise a KeyError.""" if value not in self: - raise KeyError(value) + raise KeyError("Not in set", value) self.discard(value) def pop(self): Index: Lib/collections.py =================================================================== --- Lib/collections.py (revision 83688) +++ Lib/collections.py (working copy) @@ -571,7 +571,7 @@ return self.data[key] if hasattr(self.__class__, "__missing__"): return self.__class__.__missing__(self, key) - raise KeyError(key) + raise KeyError("Not in dict", key) def __setitem__(self, key, item): self.data[key] = item def __delitem__(self, key): del self.data[key] def __iter__(self): Index: Lib/test/test_dict.py =================================================================== --- Lib/test/test_dict.py (revision 83688) +++ Lib/test/test_dict.py (working copy) @@ -546,21 +546,21 @@ f = F() with self.assertRaises(KeyError) as c: f[42] - self.assertEqual(c.exception.args, (42,)) + self.assertEqual(c.exception.args, ("Not in dict", 42,)) class G(dict): pass g = G() with self.assertRaises(KeyError) as c: g[42] - self.assertEqual(c.exception.args, (42,)) + self.assertEqual(c.exception.args, ("Not in dict", 42,)) def test_tuple_keyerror(self): # SF #1576657 d = {} with self.assertRaises(KeyError) as c: d[(1,)] - self.assertEqual(c.exception.args, ((1,),)) + self.assertEqual(c.exception.args, ("Not in dict", (1,),)) def test_bad_key(self): # Dictionary lookups should fail if __eq__() raises an exception. Index: Lib/test/test_set.py =================================================================== --- Lib/test/test_set.py (revision 83688) +++ Lib/test/test_set.py (working copy) @@ -402,21 +402,19 @@ try: self.s.remove(v1) except KeyError as e: - v2 = e.args[0] + v2 = e.args[1] self.assertEqual(v1, v2) else: self.fail() def test_remove_keyerror_set(self): key = self.thetype([3, 4]) - try: + with self.assertRaises(KeyError) as c: self.s.remove(key) - except KeyError as e: - self.assertTrue(e.args[0] is key, - "KeyError should be {0}, not {1}".format(key, - e.args[0])) - else: - self.fail() + e = c.exception + self.assertTrue(e.args[1] is key, + "KeyError should be {0}, not {1}".format(key, + e.args[1])) def test_discard(self): self.s.discard('a') Index: Lib/test/test_defaultdict.py =================================================================== --- Lib/test/test_defaultdict.py (revision 83688) +++ Lib/test/test_defaultdict.py (working copy) @@ -42,12 +42,9 @@ self.assertNotIn(12, d2.keys()) d2.default_factory = None self.assertEqual(d2.default_factory, None) - try: + with self.assertRaises(KeyError) as c: d2[15] - except KeyError as err: - self.assertEqual(err.args, (15,)) - else: - self.fail("d2[15] didn't raise KeyError") + self.assertEqual(c.exception.args, ("Not in dict", 15,)) self.assertRaises(TypeError, defaultdict, 1) def test_missing(self): @@ -142,12 +139,9 @@ def test_keyerror_without_factory(self): d1 = defaultdict() - try: + with self.assertRaises(KeyError) as c: d1[(1,)] - except KeyError as err: - self.assertEqual(err.args[0], (1,)) - else: - self.fail("expected KeyError") + self.assertEqual(c.exception.args[1], (1,)) def test_recursive_repr(self): # Issue2045: stack overflow when default_factory is a bound method Index: Lib/test/test_userdict.py =================================================================== --- Lib/test/test_userdict.py (revision 83688) +++ Lib/test/test_userdict.py (working copy) @@ -171,21 +171,15 @@ self.__missing__ = lambda key: None collections.UserDict.__init__(self) f = F() - try: + with self.assertRaises(KeyError) as c: f[42] - except KeyError as err: - self.assertEqual(err.args, (42,)) - else: - self.fail("f[42] didn't raise KeyError") + self.assertEqual(c.exception.args, ("Not in dict", 42,)) class G(collections.UserDict): pass g = G() - try: + with self.assertRaises(KeyError) as c: g[42] - except KeyError as err: - self.assertEqual(err.args, (42,)) - else: - self.fail("g[42] didn't raise KeyError") + self.assertEqual(c.exception.args, ("Not in dict", 42,)) Index: Modules/_collectionsmodule.c =================================================================== --- Modules/_collectionsmodule.c (revision 83688) +++ Modules/_collectionsmodule.c (working copy) @@ -1258,7 +1258,7 @@ PyDoc_STRVAR(defdict_missing_doc, "__missing__(key) # Called by __getitem__ for missing key; pseudo-code:\n\ - if self.default_factory is None: raise KeyError((key,))\n\ + if self.default_factory is None: raise KeyError('Not in dict', key)\n\ self[key] = value = self.default_factory()\n\ return value\n\ "); @@ -1270,9 +1270,17 @@ PyObject *value; if (factory == NULL || factory == Py_None) { /* XXX Call dict.__missing__(key) */ + static PyObject *keyerror_message = NULL; PyObject *tup; - tup = PyTuple_Pack(1, key); - if (!tup) return NULL; + if (!keyerror_message) + { + keyerror_message = PyUnicode_FromString("Not in dict"); + if (!keyerror_message) + return NULL; + } + tup = PyTuple_Pack(2, keyerror_message, key); + if (!tup) + return NULL; PyErr_SetObject(PyExc_KeyError, tup); Py_DECREF(tup); return NULL;