[Python-checkins] r86402 - in python/branches/py3k: Doc/library/configparser.rst Lib/configparser.py Lib/logging/config.py Lib/test/test_cfgparser.py

lukasz.langa python-checkins at python.org
Wed Nov 10 19:57:39 CET 2010


Author: lukasz.langa
Date: Wed Nov 10 19:57:39 2010
New Revision: 86402

Log:
Issue #5412: extend configparser to support mapping access 



Modified:
   python/branches/py3k/Doc/library/configparser.rst
   python/branches/py3k/Lib/configparser.py
   python/branches/py3k/Lib/logging/config.py
   python/branches/py3k/Lib/test/test_cfgparser.py

Modified: python/branches/py3k/Doc/library/configparser.rst
==============================================================================
--- python/branches/py3k/Doc/library/configparser.rst	(original)
+++ python/branches/py3k/Doc/library/configparser.rst	Wed Nov 10 19:57:39 2010
@@ -7,7 +7,9 @@
 .. moduleauthor:: Ken Manheimer <klm at zope.com>
 .. moduleauthor:: Barry Warsaw <bwarsaw at python.org>
 .. moduleauthor:: Eric S. Raymond <esr at thyrsus.com>
+.. moduleauthor:: Łukasz Langa <lukasz at langa.pl>
 .. sectionauthor:: Christopher G. Petrilli <petrilli at amber.org>
+.. sectionauthor:: Łukasz Langa <lukasz at langa.pl>
 
 .. index::
    pair: .ini; file
@@ -26,26 +28,219 @@
    This library does *not* interpret or write the value-type prefixes used in
    the Windows Registry extended version of INI syntax.
 
+.. seealso::
+
+   Module :mod:`shlex`
+      Support for a creating Unix shell-like mini-languages which can be used
+      as an alternate format for application configuration files.
+
+Quick Start
+-----------
+
+.. highlightlang:: none
+
+Let's take a very basic configuration file that looks like this::
+
+  [DEFAULT]
+    ServerAliveInterval = 45
+    Compression = yes
+    CompressionLevel = 9
+    ForwardX11 = yes
+
+  [bitbucket.org]
+    User = hg
+
+  [topsecret.server.com]
+    Port = 50022
+    ForwardX11 = no
+
+The supported file structure of INI files is described `in the following section
+<#supported-ini-file-structure>`_, fow now all there's to know is that the file
+consists of sections, each of which contains keys with values.
+:mod:`configparser` classes can read and write such files. Let's start by
+creating the above configuration file programatically.
+
+.. highlightlang:: python
+.. doctest::
+
+    >>> import configparser
+    >>> config = configparser.RawConfigParser()
+    >>> config['DEFAULT'] = {'ServerAliveInterval': '45',
+    ...                      'Compression': 'yes',
+    ...                      'CompressionLevel': '9'}
+    >>> config['bitbucket.org'] = {}
+    >>> config['bitbucket.org']['User'] = 'hg'
+    >>> config['topsecret.server.com'] = {}
+    >>> topsecret = config['topsecret.server.com']
+    >>> topsecret['Port'] = '50022'     # mutates the parser
+    >>> topsecret['ForwardX11'] = 'no'  # same here
+    >>> config['DEFAULT']['ForwardX11'] = 'yes'
+    >>> with open('example.ini', 'w') as configfile:
+    ...   config.write(configfile)
+    ...
+
+As you can see, we can treat a config parser just like a dictionary. There are
+a few differences, `outlined later on <#mapping-protocol-access>`_, but the
+behaviour is very close to what you'd expect from a dictionary.
+
+Now that we've created and saved a configuration file, let's try reading it
+back and exploring the data it holds.
+
+.. highlightlang:: python
+.. doctest::
+
+    >>> import configparser
+    >>> config = configparser.RawConfigParser()
+    >>> config.sections()
+    []
+    >>> config.read('example.ini')
+    ['example.ini']
+    >>> config.sections()
+    ['bitbucket.org', 'topsecret.server.com']
+    >>> 'bitbucket.org' in config
+    True
+    >>> 'bytebong.com' in config
+    False
+    >>> config['bitbucket.org']['User']
+    'hg'
+    >>> config['DEFAULT']['Compression']
+    'yes'
+    >>> topsecret = config['topsecret.server.com']
+    >>> topsecret['ForwardX11']
+    'no'
+    >>> topsecret['Port']
+    '50022'
+    >>> for key in config['bitbucket.org']: print(key)
+    ...
+    user
+    compressionlevel
+    serveraliveinterval
+    compression
+    forwardx11
+    >>> config['bitbucket.org']['ForwardX11']
+    'yes'
+
+As we can see above, the API is pretty straight forward. The only bit of magic
+involves the ``DEFAULT`` section which provides default values for all other
+sections [customizable]_. Another thing to note is that keys in sections are
+case-insensitive so they're stored in lowercase [customizable]_.
+
+Supported Datatypes
+-------------------
+
+Config parsers do not guess datatypes of values in configuration files, always
+storing them internally as strings. This means that if you need other datatypes,
+you should convert on your own:
+
+.. highlightlang:: python
+.. doctest::
+
+    >>> int(topsecret['Port'])
+    50022
+    >>> float(topsecret['CompressionLevel'])
+    9.0
+
+Converting to the boolean type is not that simple, though. Wrapping the return
+value around ``bool()`` would do us no good since ``bool('False')`` is still
+``True``. This is why config parsers also provide :meth:`getboolean`. This handy
+method is also case insensitive and correctly recognizes boolean values from
+``'yes'``/``'no'``, ``'on'``/``'off'`` and ``'1'``/``'0'`` [customizable]_.  An
+example of getting the boolean value:
+
+.. highlightlang:: python
+.. doctest::
+
+    >>> topsecret.getboolean('ForwardX11')
+    False
+    >>> config['bitbucket.org'].getboolean('ForwardX11')
+    True
+    >>> config.getboolean('bitbucket.org', 'Compression')
+    True
+
+Apart from :meth:`getboolean`, config parsers also provide equivalent
+:meth:`getint` and :meth:`getfloat` methods but these are far less useful
+because explicit casting is enough for these types.
+
+Fallback Values
+---------------
+
+As with a regular dictionary, you can use a section's :meth:`get` method to
+provide fallback values:
+
+.. highlightlang:: python
+.. doctest::
+
+    >>> topsecret.get('Port')
+    '50022'
+    >>> topsecret.get('CompressionLevel')
+    '9'
+    >>> topsecret.get('Cipher')
+    >>> topsecret.get('Cipher', '3des-cbc')
+    '3des-cbc'
+
+Please note that default values have precedence over fallback values. For
+instance, in our example the ``CompressionLevel`` key was specified only in the
+``DEFAULT`` section. If we try to get it from the section
+``topsecret.server.com``, we will always get the default, even if we specify
+a fallback:
+
+.. highlightlang:: python
+.. doctest::
+
+    >>> topsecret.get('CompressionLevel', '3')
+    '9'
+
+One more thing to be aware of is that the parser-level :meth:`get` method
+provides a custom, more complex interface, maintained for backwards
+compatibility. When using this method, a fallback value can be provided via the
+``fallback`` keyword-only argument:
+
+.. highlightlang:: python
+.. doctest::
+
+    >>> config.get('bitbucket.org', 'monster',
+    ...            fallback='No such things as monsters')
+    'No such things as monsters'
+
+The same ``fallback`` argument can be used with the :meth:`getint`,
+:meth:`getfloat` and :meth:`getboolean` methods, for example:
+
+.. highlightlang:: python
+.. doctest::
+
+    >>> 'BatchMode' in topsecret
+    False
+    >>> topsecret.getboolean('BatchMode', fallback=True)
+    True
+    >>> config['DEFAULT']['BatchMode'] = 'no'
+    >>> topsecret.getboolean('BatchMode', fallback=True)
+    False
+
+Supported INI File Structure
+----------------------------
+
 A configuration file consists of sections, each led by a ``[section]`` header,
 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
-und trailing whitespace is removed from keys and from values.  Values can be
-omitted, 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
-as parts of multiline values or ignored.
+default [customizable]_). By default, section names are case sensitive but keys
+are not [customizable]_. Leading und trailing whitespace is removed from keys and from values.
+Values can be omitted, 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 as parts of multiline values or ignored.
 
 Configuration files may include comments, prefixed by specific characters (``#``
-and ``;`` by default).  Comments may appear on their own in an otherwise empty
-line, or may be entered in lines holding values or section names.  In the
-latter case, they need to be preceded by a whitespace character to be recognized
-as a comment.  (For backwards compatibility, by default only ``;`` starts an
-inline comment, while ``#`` does not.)
+and ``;`` by default [customizable]_).  Comments may appear on their own in an
+otherwise empty line, or may be entered in lines holding values or section
+names.  In the latter case, they need to be preceded by a whitespace character
+to be recognized as a comment.  (For backwards compatibility, by default only
+``;`` starts an inline comment, while ``#`` does not [customizable]_.)
 
 On top of the core functionality, :class:`SafeConfigParser` supports
 interpolation.  This means values can contain format strings which refer to
-other values in the same section, or values in a special ``DEFAULT`` section.
-Additional defaults can be provided on initialization.
+other values in the same section, or values in a special ``DEFAULT`` section
+[customizable]_.  Additional defaults can be provided on initialization.
+
+.. highlightlang:: none
 
 For example::
 
@@ -80,7 +275,6 @@
             of a value
          # Did I mention we can indent comments, too?
 
-
 In the example above, :class:`SafeConfigParser` would resolve ``%(home_dir)s``
 to the value of ``home_dir`` (``/Users`` in this case).  ``%(my_dir)s`` in
 effect would resolve to ``/Users/lumberjack``.  All interpolations are done on
@@ -92,188 +286,438 @@
 ``my_dir``.  Other features presented in the example are handled in the same
 manner by both parsers.
 
-Default values can be specified by passing them as a dictionary when
-constructing the :class:`SafeConfigParser`.
-
-Sections are normally stored in an :class:`collections.OrderedDict` which
-maintains the order of all keys.  An alternative dictionary type can be passed
-to the :meth:`__init__` method.  For example, if a dictionary type is passed
-that sorts its keys, the sections will be sorted on write-back, as will be the
-keys within each section.
-
-
-.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
-
-   The basic configuration object.  When *defaults* is given, it is initialized
-   into the dictionary of intrinsic defaults.  When *dict_type* is given, it
-   will be used to create the dictionary objects for the list of sections, for
-   the options within a section, and for the default values.
-
-   When *delimiters* is given, it will be used as the set of substrings that
-   divide keys from values.  When *comment_prefixes* is given, it will be used
-   as the set of substrings that prefix comments in a line, both for the whole
-   line and inline comments.  For backwards compatibility, the default value for
-   *comment_prefixes* is a special value that indicates that ``;`` and ``#`` can
-   start whole line comments while only ``;`` can start inline comments.
-
-   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.
-
-   .. versionchanged:: 3.1
-      The default *dict_type* is :class:`collections.OrderedDict`.
-
-   .. versionchanged:: 3.2
-      *allow_no_value*, *delimiters*, *comment_prefixes*, *strict* and
-      *empty_lines_in_values* were added.
-
-
-.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
-
-   Derived class of :class:`ConfigParser` that implements a sane variant of the
-   magical interpolation feature.  This implementation is more predictable as it
-   validates the interpolation syntax used within a configuration file.  This
-   class also enables escaping the interpolation character (e.g. a key can have
-   ``%`` as part of the value by specifying ``%%`` in the file).
-
-   Applications that don't require interpolation should use
-   :class:`RawConfigParser`, otherwise :class:`SafeConfigParser` is the best
-   option.
-
-   .. versionchanged:: 3.1
-      The default *dict_type* is :class:`collections.OrderedDict`.
-
-   .. versionchanged:: 3.2
-      *allow_no_value*, *delimiters*, *comment_prefixes*, *strict* and
-      *empty_lines_in_values* were added.
-
-
-.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
-
-   Derived class of :class:`RawConfigParser` that implements the magical
-   interpolation feature and adds optional arguments to the :meth:`get` and
-   :meth:`items` methods.
-
-   :class:`SafeConfigParser` is generally recommended over this class if you
-   need interpolation.
-
-   The values in *defaults* must be appropriate for the ``%()s`` string
-   interpolation.  Note that *__name__* is an intrinsic default; its value is
-   the section name, and will override any value provided in *defaults*.
+Mapping Protocol Access
+-----------------------
 
-   All option names used in interpolation will be passed through the
-   :meth:`optionxform` method just like any other option name reference.  For
-   example, using the default implementation of :meth:`optionxform` (which
-   converts option names to lower case), the values ``foo %(bar)s`` and ``foo
-   %(BAR)s`` are equivalent.
+.. versionadded:: 3.2
+.. highlightlang:: python
 
-   .. versionchanged:: 3.1
-      The default *dict_type* is :class:`collections.OrderedDict`.
+Mapping protocol access is a generic name for functionality that enables using
+custom objects as if they were dictionaries. In case of :mod:`configparser`,
+the mapping interface implementation is using the
+``parser['section']['option']`` notation.
+
+``parser['section']`` in particular returns a proxy for the section's data in
+the parser. This means that the values are not copied but they are taken from
+the original parser on demand. What's even more important is that when values
+are changed on a section proxy, they are actually mutated in the original
+parser.
+
+:mod:`configparser` objects behave as close to actual dictionaries as possible.
+The mapping interface is complete and adheres to the ``MutableMapping`` ABC.
+However, there are a few differences that should be taken into account:
+
+* by default, all keys in sections are accessible in a case-insensitive manner
+  [customizable]_. E.g. ``for option in parser["section"]`` yields only
+  ``optionxform``'ed option key names. This means lowercased keys by default.
+  At the same time, for a section that holds the key ``"a"``, both expressions
+  return ``True``::
+
+  "a" in parser["section"]
+  "A" in parser["section"]
+
+* all sections include ``DEFAULTSECT`` values as well which means that
+  ``.clear()`` on a section may not leave the section visibly empty. This is
+  because default values cannot be deleted from the section (because technically
+  they are not there). If they are overriden in the section, deleting causes the
+  default value to be visible again. Trying to delete a default value causes
+  a ``KeyError``.
+
+* trying to delete the ``DEFAULTSECT`` throws ``ValueError``
+
+* there are two parser-level methods in the legacy API that hide
+  the dictionary interface and are incompatible:
+
+  * ``parser.get(section, option, **kwargs)`` - the second argument is **not**
+    a fallback value
+
+  * ``parser.items(section)`` - this returns a list of ``(option, value)``
+    pairs for a specified ``section``.
+
+The mapping protocol is implemented on top of the existing legacy API so that
+subclassing the original interface makes the mappings work as expected as well.
+One difference is the explicit lack of support for the `__name__` special key.
+This is because the existing behaviour of `__name__` is very inconsistent and
+supporting it would only lead to problems. Details `here
+<http://mail.python.org/pipermail/python-dev/2010-July/102556.html>`_.
+
+Customizing Parser Behaviour
+----------------------------
+
+There are nearly as many INI format variants as there are applications using it.
+:mod:`configparser` goes a long way to provide support for the largest sensible
+set of INI styles available. The default functionality is mainly dictated by
+historical background and it's very likely that you will want to customize some
+of the features.
+
+The most natural way to change the way a specific config parser works is to use
+the :meth:`__init__` options:
+
+* *defaults*, default value: ``None``
+
+  This option accepts a dictionary of key-value pairs which will be initially
+  put in the ``DEFAULTSECT``. This makes for an elegant way to support concise
+  configuration files that don't specify values which are the same as the
+  documented default.
+
+  Hint: if you want to specify default values for a specific section, use the
+  :meth:`read_dict` before you read the actual file.
+
+* *dict_type*, default value: :class:`collections.OrderedDict`
+
+  This option has a major impact on how the mapping protocol will behave and how
+  the written configuration files will look like. With the default ordered
+  dictionary, every section is stored in the order they were added to the
+  parser. Same goes for options within sections.
+
+  An alternative dictionary type can be used for example to sort sections and
+  options on write-back. You can also use a regular dictionary for performance
+  reasons.
+
+  Please note: there are ways to add a set of key-value pairs in a single
+  operation. When you use a regular dictionary in those operations, the order of
+  the keys may be random. For example:
+
+  .. highlightlang:: python
+  .. doctest::
+
+    >>> parser = configparser.RawConfigParser()
+    >>> parser.read_dict({'section1': {'key1': 'value1',
+    ...                                'key2': 'value2',
+    ...                                'key3': 'value3'},
+    ...                   'section2': {'keyA': 'valueA',
+    ...                                'keyB': 'valueB',
+    ...                                'keyC': 'valueC'},
+    ...                   'section3': {'foo': 'x',
+    ...                                'bar': 'y',
+    ...                                'baz': 'z'}
+    ... })
+    >>> parser.sections()
+    ['section3', 'section2', 'section1']
+    >>> [option for option in parser['section3']]
+    ['baz', 'foo', 'bar']
+
+  In these operations you need to use an ordered dictionary as well:
+
+  .. highlightlang:: python
+  .. doctest::
+
+    >>> from collections import OrderedDict
+    >>> parser = configparser.RawConfigParser()
+    >>> parser.read_dict(
+    ...   OrderedDict((
+    ...     ('s1',
+    ...      OrderedDict((
+    ...        ('1', '2'),
+    ...        ('3', '4'),
+    ...        ('5', '6'),
+    ...      ))
+    ...     ),
+    ...     ('s2',
+    ...      OrderedDict((
+    ...        ('a', 'b'),
+    ...        ('c', 'd'),
+    ...        ('e', 'f'),
+    ...      ))
+    ...     ),
+    ...   ))
+    ... )
+    >>> parser.sections()
+    ['s1', 's2']
+    >>> [option for option in parser['s1']]
+    ['1', '3', '5']
+    >>> [option for option in parser['s2'].values()]
+    ['b', 'd', 'f']
+
+* *allow_no_value*, default value: ``False``
+
+  Some configuration files are known to include settings without values, but
+  which otherwise conform to the syntax supported by :mod:`configparser`.  The
+  *allow_no_value* parameter to the :meth:`__init__` method can be used to
+  indicate that such values should be accepted:
+
+  .. highlightlang:: python
+  .. doctest::
+
+    >>> import configparser
+
+    >>> sample_config = """
+    ... [mysqld]
+    ...   user = mysql
+    ...   pid-file = /var/run/mysqld/mysqld.pid
+    ...   skip-external-locking
+    ...   old_passwords = 1
+    ...   skip-bdb
+    ...   skip-innodb # we don't need ACID today
+    ... """
+    >>> config = configparser.RawConfigParser(allow_no_value=True)
+    >>> config.read_string(sample_config)
+
+    >>> # Settings with values are treated as before:
+    >>> config["mysqld"]["user"]
+    'mysql'
+
+    >>> # Settings without values provide None:
+    >>> config["mysqld"]["skip-bdb"]
+
+    >>> # Settings which aren't specified still raise an error:
+    >>> config["mysqld"]["does-not-exist"]
+    Traceback (most recent call last):
+      ...
+    KeyError: 'does-not-exist'
 
-   .. versionchanged:: 3.2
-      *allow_no_value*, *delimiters*, *comment_prefixes*,
-      *strict* and *empty_lines_in_values* were added.
+* *delimiters*, default value: ``('=', ':')``
 
+  Delimiters are substrings that delimit keys from values within a section. The
+  first occurence of a delimiting substring on a line is considered a delimiter.
+  This means values (but not keus) can contain substrings that are in the
+  *delimiters*.
+
+  See also the *space_around_delimiters* argument to
+  :meth:`RawConfigParser.write`.
+
+* *comment_prefixes*, default value: ``_COMPATIBLE`` (``'#'`` valid on empty
+  lines, ``';'`` valid also on non-empty lines)
+
+  Comment prefixes are substrings that indicate the start of a valid comment
+  within a config file. The peculiar default value allows for comments starting
+  with ``'#'`` or ``';'`` but only the latter can be used in a non-empty line.
+  This is obviously dictated by backwards compatibiliy. A more predictable
+  approach would be to specify prefixes as ``('#', ';')`` which will allow for
+  both prefixes to be used in non-empty lines.
+
+  Please note that config parsers don't support escaping of comment prefixes so
+  leaving characters out of *comment_prefixes* is a way of ensuring they can be
+  used as parts of keys or values.
+
+* *strict*, default value: ``False``
+
+  If set to ``True``, the parser will not allow for any section or option
+  duplicates while reading from a single source (using :meth:`read_file`,
+  :meth:`read_string` or :meth:`read_dict`). The default is ``False`` only
+  because of backwards compatibility reasons. It's recommended to use strict
+  parsers in new applications.
+
+* *empty_lines_in_values*, default value: ``True``
+
+  .. highlightlang:: none
+
+  In config parsers, values can be multiline as long as they're indented deeper
+  than the key that holds them. By default parsers also let empty lines to be
+  parts of values. At the same time, keys can be arbitrarily indented themselves
+  to improve readability. In consequence, when configuration files get big and
+  complex, it's easy for the user to lose track of the file structure. Take for
+  instance::
+
+    [Section]
+    key = multiline
+      value with a gotcha
+
+     this = is still a part of the multiline value of 'key'
+
+
+  This can be especially problematic for the user to see if she's using
+  a proportional font to edit the file. That's why when your application does
+  not need values with empty lines, you should consider disallowing them. This
+  will make empty lines split keys every time. In the example above, it would
+  produce two keys, ``key`` and ``this``.
+
+.. highlightlang:: python
+
+More advanced customization may be achieved by overriding default values of the
+following parser members:
+
+* `RawConfigParser.BOOLEAN_STATES`
+
+  By default when using :meth:`getboolean`, config parsers consider the
+  following values ``True``: ``'1'``, ``'yes'``, ``'true'``, ``'on'`` and the
+  following values ``False``: ``'0'``, ``'no'``, ``'false'``, ``'off'``. You can
+  override this by specifying a custom dictionary of strings and their boolean
+  outcomes. For example:
+
+  .. highlightlang:: python
+  .. doctest::
+
+    >>> custom = configparser.RawConfigParser()
+    >>> custom['section1'] = {'funky': 'nope'}
+    >>> custom['section1'].getboolean('funky')
+    Traceback (most recent call last):
+    ...
+    ValueError: Not a boolean: nope
+    >>> custom.BOOLEAN_STATES = {'sure': True, 'nope': False}
+    >>> custom['section1'].getboolean('funky')
+    False
+
+  Other typical boolean pairs include ``accept``/``reject`` or
+  ``enabled``/``disabled``.
+
+* :meth:`RawConfigParser.optionxform`
+
+  This is a method that transforms option names on every read or set operation.
+  By default it converts the name to lowercase. This also means that when
+  a configuration file gets written, all keys will be lowercase. If you find
+  that behaviour unsuitable, you can override this method.  For example:
+
+  .. highlightlang:: python
+  .. doctest::
+
+    >>> config = """
+    ... [Section1]
+    ... Key = Value
+    ...
+    ... [Section2]
+    ... AnotherKey = Value
+    ... """
+    >>> typical = configparser.RawConfigParser()
+    >>> typical.read_string(config)
+    >>> list(typical['Section1'].keys())
+    ['key']
+    >>> list(typical['Section2'].keys())
+    ['anotherkey']
+    >>> custom = configparser.RawConfigParser()
+    >>> custom.optionxform = lambda option: option
+    >>> custom.read_string(config)
+    >>> list(custom['Section1'].keys())
+    ['Key']
+    >>> list(custom['Section2'].keys())
+    ['AnotherKey']
+
+Legacy API Examples
+-------------------
+
+Mainly because of backwards compatibility concerns, :mod:`configparser`
+provides also a legacy API with explicit ``get``/``set`` methods. While there
+are valid use cases for the methods outlined below, mapping protocol access
+is preferred for new projects. The legacy API is at times more advanced,
+low-level and downright counterintuitive.
 
-.. exception:: Error
+An example of writing to a configuration file::
 
-   Base class for all other configparser exceptions.
+   import configparser
 
+   config = configparser.RawConfigParser()
 
-.. exception:: NoSectionError
+   # Please note that using RawConfigParser's and the raw mode of
+   # ConfigParser's respective set functions, you can assign non-string values
+   # to keys internally, but will receive an error when attempting to write to
+   # a file or when you get it in non-raw mode. Setting values using the
+   # mapping protocol or SafeConfigParser's set() does not allow such
+   # assignments to take place.
+   config.add_section('Section1')
+   config.set('Section1', 'int', '15')
+   config.set('Section1', 'bool', 'true')
+   config.set('Section1', 'float', '3.1415')
+   config.set('Section1', 'baz', 'fun')
+   config.set('Section1', 'bar', 'Python')
+   config.set('Section1', 'foo', '%(bar)s is %(baz)s!')
 
-   Exception raised when a specified section is not found.
+   # Writing our configuration file to 'example.cfg'
+   with open('example.cfg', 'w') as configfile:
+       config.write(configfile)
 
+An example of reading the configuration file again::
 
-.. exception:: DuplicateSectionError
+   import configparser
 
-   Exception raised if :meth:`add_section` is called with the name of a section
-   that is already present or in strict parsers when a section if found more
-   than once in a single input file, string or dictionary.
+   config = configparser.RawConfigParser()
+   config.read('example.cfg')
 
-   .. versionadded:: 3.2
-      Optional ``source`` and ``lineno`` attributes and arguments to
-      :meth:`__init__` were added.
+   # getfloat() raises an exception if the value is not a float
+   # getint() and getboolean() also do this for their respective types
+   float = config.getfloat('Section1', 'float')
+   int = config.getint('Section1', 'int')
+   print(float + int)
 
+   # Notice that the next output does not interpolate '%(bar)s' or '%(baz)s'.
+   # This is because we are using a RawConfigParser().
+   if config.getboolean('Section1', 'bool'):
+       print(config.get('Section1', 'foo'))
 
-.. exception:: DuplicateOptionError
+To get interpolation, you will need to use a :class:`SafeConfigParser` or, if
+you absolutely have to, a :class:`ConfigParser`::
 
-   Exception raised by strict parsers if a single option appears twice during
-   reading from a single file, string or dictionary. This catches misspellings
-   and case sensitivity-related errors, e.g. a dictionary may have two keys
-   representing the same case-insensitive configuration key.
+   import configparser
 
+   cfg = configparser.SafeConfigParser()
+   cfg.read('example.cfg')
 
-.. exception:: NoOptionError
+   # Set the optional `raw` argument of get() to True if you wish to disable
+   # interpolation in a single get operation.
+   print(cfg.get('Section1', 'foo', raw=False)) # -> "Python is fun!"
+   print(cfg.get('Section1', 'foo', raw=True))  # -> "%(bar)s is %(baz)s!"
 
-   Exception raised when a specified option is not found in the specified
-   section.
+   # The optional `vars` argument is a dict with members that will take
+   # precedence in interpolation.
+   print(cfg.get('Section1', 'foo', vars={'bar': 'Documentation',
+                                             'baz': 'evil'}))
 
+   # The optional `fallback` argument can be used to provide a fallback value
+   print(cfg.get('Section1', 'foo'))
+         # -> "Python is fun!"
 
-.. exception:: InterpolationError
+   print(cfg.get('Section1', 'foo', fallback='Monty is not.'))
+         # -> "Python is fun!"
 
-   Base class for exceptions raised when problems occur performing string
-   interpolation.
+   print(cfg.get('Section1', 'monster', fallback='No such things as monsters.'))
+         # -> "No such things as monsters."
 
+   # A bare print(cfg.get('Section1', 'monster')) would raise NoOptionError
+   # but we can also use:
 
-.. exception:: InterpolationDepthError
+   print(cfg.get('Section1', 'monster', fallback=None))
+         # -> None
 
-   Exception raised when string interpolation cannot be completed because the
-   number of iterations exceeds :const:`MAX_INTERPOLATION_DEPTH`. Subclass of
-   :exc:`InterpolationError`.
 
+Defaults are available in all three types of ConfigParsers. They are used in
+interpolation if an option used is not defined elsewhere. ::
 
-.. exception:: InterpolationMissingOptionError
+   import configparser
 
-   Exception raised when an option referenced from a value does not exist. Subclass
-   of :exc:`InterpolationError`.
+   # New instance with 'bar' and 'baz' defaulting to 'Life' and 'hard' each
+   config = configparser.SafeConfigParser({'bar': 'Life', 'baz': 'hard'})
+   config.read('example.cfg')
 
+   print(config.get('Section1', 'foo')) # -> "Python is fun!"
+   config.remove_option('Section1', 'bar')
+   config.remove_option('Section1', 'baz')
+   print(config.get('Section1', 'foo')) # -> "Life is hard!"
 
-.. exception:: InterpolationSyntaxError
+.. _rawconfigparser-objects:
 
-   Exception raised when the source text into which substitutions are made does not
-   conform to the required syntax. Subclass of :exc:`InterpolationError`.
+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)
 
-.. exception:: MissingSectionHeaderError
+   The basic configuration object.  When *defaults* is given, it is initialized
+   into the dictionary of intrinsic defaults.  When *dict_type* is given, it
+   will be used to create the dictionary objects for the list of sections, for
+   the options within a section, and for the default values.
 
-   Exception raised when attempting to parse a file which has no section headers.
+   When *delimiters* is given, it will be used as the set of substrings that
+   divide keys from values.  When *comment_prefixes* is given, it will be used
+   as the set of substrings that prefix comments in a line, both for the whole
+   line and inline comments.  For backwards compatibility, the default value for
+   *comment_prefixes* is a special value that indicates that ``;`` and ``#`` can
+   start whole line comments while only ``;`` can start inline comments.
 
+   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``.
 
-.. exception:: ParsingError
+   This class does not support the magical interpolation behavior.
 
-   Exception raised when errors occur attempting to parse a file.
+   .. versionchanged:: 3.1
+      The default *dict_type* is :class:`collections.OrderedDict`.
 
    .. versionchanged:: 3.2
-      The ``filename`` attribute and :meth:`__init__` argument were renamed to
-      ``source`` for consistency.
-
-.. data:: MAX_INTERPOLATION_DEPTH
-
-   The maximum depth for recursive interpolation for :meth:`get` when the *raw*
-   parameter is false.  This is relevant only for the :class:`ConfigParser` class.
-
-
-.. seealso::
-
-   Module :mod:`shlex`
-      Support for a creating Unix shell-like mini-languages which can be used as an
-      alternate format for application configuration files.
-
-
-.. _rawconfigparser-objects:
-
-RawConfigParser Objects
------------------------
-
-:class:`RawConfigParser` instances have the following methods:
+      *allow_no_value*, *delimiters*, *comment_prefixes*, *strict* and
+      *empty_lines_in_values* were added.
 
 
 .. method:: RawConfigParser.defaults()
@@ -373,29 +817,34 @@
 
    .. versionadded:: 3.2
 
-.. method:: RawConfigParser.get(section, option, [vars, default])
+.. method:: RawConfigParser.get(section, option, [vars, fallback])
 
    Get an *option* value for the named *section*. If *vars* is provided, it
    must be a dictionary.  The *option* is looked up in *vars* (if provided),
    *section*, and in *DEFAULTSECT* in that order. If the key is not found and
-   *default* is provided, it is used as a fallback value. ``None`` can be
-   provided as a *default* value.
+   *fallback* is provided, it is used as a fallback value. ``None`` can be
+   provided as a *fallback* value.
+
+   .. versionchanged:: 3.2
+      Arguments *vars* and *fallback* are keyword only to protect users from
+      trying to use the third argument as the *fallback* fallback (especially
+      when using the mapping protocol).
 
 
-.. method:: RawConfigParser.getint(section, option, [vars, default])
+.. method:: RawConfigParser.getint(section, option, [vars, fallback])
 
    A convenience method which coerces the *option* in the specified *section* to
-   an integer. See :meth:`get` for explanation of *vars* and *default*.
+   an integer. See :meth:`get` for explanation of *vars* and *fallback*.
 
 
-.. method:: RawConfigParser.getfloat(section, option, [vars, default])
+.. method:: RawConfigParser.getfloat(section, option, [vars, fallback])
 
    A convenience method which coerces the *option* in the specified *section* to
    a floating point number.  See :meth:`get` for explanation of *vars* and
-   *default*.
+   *fallback*.
 
 
-.. method:: RawConfigParser.getboolean(section, option, [vars, default])
+.. method:: RawConfigParser.getboolean(section, option, [vars, fallback])
 
    A convenience method which coerces the *option* in the specified *section*
    to a Boolean value.  Note that the accepted values for the option are
@@ -403,30 +852,39 @@
    return ``True``, and ``"0"``, ``"no"``, ``"false"``, and ``"off"``, which
    cause it to return ``False``.  These string values are checked in
    a case-insensitive manner.  Any other value will cause it to raise
-   :exc:`ValueError`. See :meth:`get` for explanation of *vars* and *default*.
+   :exc:`ValueError`. See :meth:`get` for explanation of *vars* and *fallback*.
 
 
 .. method:: RawConfigParser.items(section)
 
-   Return a list of ``(name, value)`` pairs for each option in the given *section*.
+   Return a list of ``(name, value)`` pairs for each option in the given
+   *section*.
 
 
 .. method:: RawConfigParser.set(section, option, value)
 
    If the given section exists, set the given option to the specified value;
    otherwise raise :exc:`NoSectionError`.  While it is possible to use
-   :class:`RawConfigParser` (or :class:`ConfigParser` with *raw* parameters set to
-   true) for *internal* storage of non-string values, full functionality (including
-   interpolation and output to files) can only be achieved using string values.
+   :class:`RawConfigParser` (or :class:`ConfigParser` with *raw* parameters set
+   to true) for *internal* storage of non-string values, full functionality
+   (including interpolation and output to files) can only be achieved using
+   string values.
+
+.. warning::
+
+   This method lets users assign non-string values to keys internally. This
+   behaviour is unsupported and will cause errors when attempting to write to
+   a file or get it in non-raw mode. **Use the mapping protocol API** which does
+   not allow such assignments to take place.
 
 
 .. method:: RawConfigParser.write(fileobject, space_around_delimiters=True)
 
-   Write a representation of the configuration to the specified :term:`file object`,
-   which must be opened in text mode (accepting strings).  This representation
-   can be parsed by a future :meth:`read` call. If ``space_around_delimiters``
-   is ``True`` (the default), delimiters between keys and values are surrounded
-   by spaces.
+   Write a representation of the configuration to the specified
+   :term:`file object`, which must be opened in text mode (accepting strings).
+   This representation can be parsed by a future :meth:`read` call. If
+   ``space_around_delimiters`` is ``True`` (the default), delimiters between
+   keys and values are surrounded by spaces.
 
 
 .. method:: RawConfigParser.remove_option(section, option)
@@ -474,39 +932,72 @@
 ConfigParser Objects
 --------------------
 
+.. warning::
+   Whenever you can, consider using :class:`SafeConfigParser` which
+   adds validation and escaping for the interpolation.
+
 The :class:`ConfigParser` class extends some methods of the
-:class:`RawConfigParser` interface, adding some optional arguments. Whenever you
-can, consider using :class:`SafeConfigParser` which adds validation and escaping
-for the interpolation.
+:class:`RawConfigParser` interface, adding some optional arguments.
+
+.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
 
+   Derived class of :class:`RawConfigParser` that implements the magical
+   interpolation feature and adds optional arguments to the :meth:`get` and
+   :meth:`items` methods.
 
-.. method:: ConfigParser.get(section, option, raw=False, [vars, default])
+   :class:`SafeConfigParser` is generally recommended over this class if you
+   need interpolation.
+
+   The values in *defaults* must be appropriate for the ``%()s`` string
+   interpolation.  Note that *__name__* is an intrinsic default; its value is
+   the section name, and will override any value provided in *defaults*.
+
+   All option names used in interpolation will be passed through the
+   :meth:`optionxform` method just like any other option name reference.  For
+   example, using the default implementation of :meth:`optionxform` (which
+   converts option names to lower case), the values ``foo %(bar)s`` and ``foo
+   %(BAR)s`` are equivalent.
+
+   .. versionchanged:: 3.1
+      The default *dict_type* is :class:`collections.OrderedDict`.
+
+   .. versionchanged:: 3.2
+      *allow_no_value*, *delimiters*, *comment_prefixes*,
+      *strict* and *empty_lines_in_values* were added.
+
+
+.. method:: ConfigParser.get(section, option, raw=False, [vars, fallback])
 
    Get an *option* value for the named *section*.  If *vars* is provided, it
    must be a dictionary.  The *option* is looked up in *vars* (if provided),
    *section*, and in *DEFAULTSECT* in that order. If the key is not found and
-   *default* is provided, it is used as a fallback value. ``None`` can be
-   provided as a *default* value.
+   *fallback* is provided, it is used as a fallback value. ``None`` can be
+   provided as a *fallback* value.
 
    All the ``'%'`` interpolations are expanded in the return values, unless the
    *raw* argument is true.  Values for interpolation keys are looked up in the
    same manner as the option.
 
+   .. versionchanged:: 3.2
+      Arguments *raw*, *vars* and *fallback* are keyword only to protect users
+      from trying to use the third argument as the *fallback* fallback
+      (especially when using the mapping protocol).
+
 
-.. method:: ConfigParser.getint(section, option, raw=False, [vars, default])
+.. method:: ConfigParser.getint(section, option, raw=False, [vars, fallback])
 
    A convenience method which coerces the *option* in the specified *section* to
-   an integer. See :meth:`get` for explanation of *raw*, *vars* and *default*.
+   an integer. See :meth:`get` for explanation of *raw*, *vars* and *fallback*.
 
 
-.. method:: ConfigParser.getfloat(section, option, raw=False, [vars, default])
+.. method:: ConfigParser.getfloat(section, option, raw=False, [vars, fallback])
 
    A convenience method which coerces the *option* in the specified *section* to
    a floating point number. See :meth:`get` for explanation of *raw*, *vars*
-   and *default*.
+   and *fallback*.
 
 
-.. method:: ConfigParser.getboolean(section, option, raw=False, [vars, default])
+.. method:: ConfigParser.getboolean(section, option, raw=False, [vars, fallback])
 
    A convenience method which coerces the *option* in the specified *section*
    to a Boolean value.  Note that the accepted values for the option are
@@ -515,7 +1006,7 @@
    cause it to return ``False``.  These string values are checked in
    a case-insensitive manner.  Any other value will cause it to raise
    :exc:`ValueError`. See :meth:`get` for explanation of *raw*, *vars* and
-   *default*.
+   *fallback*.
 
 
 .. method:: ConfigParser.items(section, raw=False, vars=None)
@@ -525,15 +1016,39 @@
    method.
 
 
+.. data:: MAX_INTERPOLATION_DEPTH
+
+   The maximum depth for recursive interpolation for :meth:`get` when the *raw*
+   parameter is false.  This is relevant only for the :class:`ConfigParser` class.
+
 .. _safeconfigparser-objects:
 
 SafeConfigParser Objects
 ------------------------
 
+.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
+
+   Derived class of :class:`ConfigParser` that implements a sane variant of the
+   magical interpolation feature.  This implementation is more predictable as it
+   validates the interpolation syntax used within a configuration file.  This
+   class also enables escaping the interpolation character (e.g. a key can have
+   ``%`` as part of the value by specifying ``%%`` in the file).
+
+   Applications that don't require interpolation should use
+   :class:`RawConfigParser`, otherwise :class:`SafeConfigParser` is the best
+   option.
+
+   .. versionchanged:: 3.1
+      The default *dict_type* is :class:`collections.OrderedDict`.
+
+   .. versionchanged:: 3.2
+      *allow_no_value*, *delimiters*, *comment_prefixes*, *strict* and
+      *empty_lines_in_values* were added.
+
+
 The :class:`SafeConfigParser` class implements the same extended interface as
 :class:`ConfigParser`, with the following addition:
 
-
 .. method:: SafeConfigParser.set(section, option, value)
 
    If the given section exists, set the given option to the specified value;
@@ -541,126 +1056,82 @@
    not, :exc:`TypeError` is raised.
 
 
-Examples
---------
+Exceptions
+----------
 
-An example of writing to a configuration file::
+.. exception:: Error
 
-   import configparser
+   Base class for all other configparser exceptions.
 
-   config = configparser.RawConfigParser()
 
-   # When adding sections or items, add them in the reverse order of
-   # how you want them to be displayed in the actual file.
-   # In addition, please note that using RawConfigParser's and the raw
-   # mode of ConfigParser's respective set functions, you can assign
-   # non-string values to keys internally, but will receive an error
-   # when attempting to write to a file or when you get it in non-raw
-   # mode. SafeConfigParser does not allow such assignments to take place.
-   config.add_section('Section1')
-   config.set('Section1', 'int', '15')
-   config.set('Section1', 'bool', 'true')
-   config.set('Section1', 'float', '3.1415')
-   config.set('Section1', 'baz', 'fun')
-   config.set('Section1', 'bar', 'Python')
-   config.set('Section1', 'foo', '%(bar)s is %(baz)s!')
+.. exception:: NoSectionError
 
-   # Writing our configuration file to 'example.cfg'
-   with open('example.cfg', 'w') as configfile:
-       config.write(configfile)
+   Exception raised when a specified section is not found.
 
-An example of reading the configuration file again::
 
-   import configparser
+.. exception:: DuplicateSectionError
 
-   config = configparser.RawConfigParser()
-   config.read('example.cfg')
+   Exception raised if :meth:`add_section` is called with the name of a section
+   that is already present or in strict parsers when a section if found more
+   than once in a single input file, string or dictionary.
 
-   # getfloat() raises an exception if the value is not a float
-   # getint() and getboolean() also do this for their respective types
-   float = config.getfloat('Section1', 'float')
-   int = config.getint('Section1', 'int')
-   print(float + int)
+   .. versionadded:: 3.2
+      Optional ``source`` and ``lineno`` attributes and arguments to
+      :meth:`__init__` were added.
 
-   # Notice that the next output does not interpolate '%(bar)s' or '%(baz)s'.
-   # This is because we are using a RawConfigParser().
-   if config.getboolean('Section1', 'bool'):
-       print(config.get('Section1', 'foo'))
 
-To get interpolation, you will need to use a :class:`ConfigParser` or
-:class:`SafeConfigParser`::
+.. exception:: DuplicateOptionError
 
-   import configparser
+   Exception raised by strict parsers if a single option appears twice during
+   reading from a single file, string or dictionary. This catches misspellings
+   and case sensitivity-related errors, e.g. a dictionary may have two keys
+   representing the same case-insensitive configuration key.
 
-   config = configparser.ConfigParser()
-   config.read('example.cfg')
 
-   # Set the third, optional argument of get to 1 if you wish to use raw mode.
-   print(config.get('Section1', 'foo', 0)) # -> "Python is fun!"
-   print(config.get('Section1', 'foo', 1)) # -> "%(bar)s is %(baz)s!"
+.. exception:: NoOptionError
 
-   # The optional fourth argument is a dict with members that will take
-   # precedence in interpolation.
-   print(config.get('Section1', 'foo', 0, {'bar': 'Documentation',
-                                           'baz': 'evil'}))
+   Exception raised when a specified option is not found in the specified
+   section.
 
-Defaults are available in all three types of ConfigParsers. They are used in
-interpolation if an option used is not defined elsewhere. ::
 
-   import configparser
+.. exception:: InterpolationError
 
-   # New instance with 'bar' and 'baz' defaulting to 'Life' and 'hard' each
-   config = configparser.SafeConfigParser({'bar': 'Life', 'baz': 'hard'})
-   config.read('example.cfg')
+   Base class for exceptions raised when problems occur performing string
+   interpolation.
 
-   print(config.get('Section1', 'foo')) # -> "Python is fun!"
-   config.remove_option('Section1', 'bar')
-   config.remove_option('Section1', 'baz')
-   print(config.get('Section1', 'foo')) # -> "Life is hard!"
 
-The function ``opt_move`` below can be used to move options between sections::
+.. exception:: InterpolationDepthError
 
-   def opt_move(config, section1, section2, option):
-       try:
-           config.set(section2, option, config.get(section1, option, 1))
-       except configparser.NoSectionError:
-           # Create non-existent section
-           config.add_section(section2)
-           opt_move(config, section1, section2, option)
-       else:
-           config.remove_option(section1, option)
-
-Some configuration files are known to include settings without values, but which
-otherwise conform to the syntax supported by :mod:`configparser`.  The
-*allow_no_value* parameter to the :meth:`__init__` method can be used to
-indicate that such values should be accepted:
+   Exception raised when string interpolation cannot be completed because the
+   number of iterations exceeds :const:`MAX_INTERPOLATION_DEPTH`. Subclass of
+   :exc:`InterpolationError`.
 
-.. doctest::
 
-   >>> import configparser
-   >>> import io
+.. exception:: InterpolationMissingOptionError
+
+   Exception raised when an option referenced from a value does not exist. Subclass
+   of :exc:`InterpolationError`.
+
+
+.. exception:: InterpolationSyntaxError
+
+   Exception raised when the source text into which substitutions are made does not
+   conform to the required syntax. Subclass of :exc:`InterpolationError`.
+
+
+.. exception:: MissingSectionHeaderError
+
+   Exception raised when attempting to parse a file which has no section headers.
+
+
+.. exception:: ParsingError
+
+   Exception raised when errors occur attempting to parse a file.
+
+   .. versionchanged:: 3.2
+      The ``filename`` attribute and :meth:`__init__` argument were renamed to
+      ``source`` for consistency.
 
-   >>> sample_config = """
-   ... [mysqld]
-   ...   user = mysql
-   ...   pid-file = /var/run/mysqld/mysqld.pid
-   ...   skip-external-locking
-   ...   old_passwords = 1
-   ...   skip-bdb
-   ...   skip-innodb # we don't need ACID today
-   ... """
-   >>> config = configparser.RawConfigParser(allow_no_value=True)
-   >>> config.read_file(io.BytesIO(sample_config))
-
-   >>> # Settings with values are treated as before:
-   >>> config.get("mysqld", "user")
-   'mysql'
-
-   >>> # Settings without values provide None:
-   >>> config.get("mysqld", "skip-bdb")
-
-   >>> # Settings which aren't specified still raise an error:
-   >>> config.get("mysqld", "does-not-exist")
-   Traceback (most recent call last):
-     ...
-   configparser.NoOptionError: No option 'does-not-exist' in section: 'mysqld'
+.. [customizable] Config parsers allow for very heavy customization. If you're
+                  interested in changing the behaviour outlined by the footnote
+                  reference, consult the `Customizing Parser Behaviour`_ section.

Modified: python/branches/py3k/Lib/configparser.py
==============================================================================
--- python/branches/py3k/Lib/configparser.py	(original)
+++ python/branches/py3k/Lib/configparser.py	Wed Nov 10 19:57:39 2010
@@ -85,7 +85,7 @@
         and their keys will be added in order. Values are automatically
         converted to strings.
 
-    get(section, option, raw=False, vars=None, default=_UNSET)
+    get(section, option, raw=False, vars=None, fallback=_UNSET)
         Return a string value for the named option.  All % interpolations are
         expanded in the return values, based on the defaults passed into the
         constructor and the DEFAULT section.  Additional substitutions may be
@@ -93,13 +93,13 @@
         contents override any pre-existing defaults. If `option' is a key in
         `vars', the value from `vars' is used.
 
-    getint(section, options, raw=False, vars=None, default=_UNSET)
+    getint(section, options, raw=False, vars=None, fallback=_UNSET)
         Like get(), but convert value to an integer.
 
-    getfloat(section, options, raw=False, vars=None, default=_UNSET)
+    getfloat(section, options, raw=False, vars=None, fallback=_UNSET)
         Like get(), but convert value to a float.
 
-    getboolean(section, options, raw=False, vars=None, default=_UNSET)
+    getboolean(section, options, raw=False, vars=None, fallback=_UNSET)
         Like get(), but convert value to a boolean (currently case
         insensitively defined as 0, false, no, off for False, and 1, true,
         yes, on for True).  Returns False or True.
@@ -123,13 +123,10 @@
         between keys and values are surrounded by spaces.
 """
 
-try:
-    from collections import OrderedDict as _default_dict
-except ImportError:
-    # fallback for setup.py which hasn't yet built _collections
-    _default_dict = dict
-
+from collections import MutableMapping, OrderedDict as _default_dict
+import functools
 import io
+import itertools
 import re
 import sys
 import warnings
@@ -366,7 +363,7 @@
 _UNSET = object()
 
 
-class RawConfigParser:
+class RawConfigParser(MutableMapping):
     """ConfigParser that does not do interpolation."""
 
     # Regular expressions for parsing section headers and options
@@ -413,6 +410,8 @@
         self._dict = dict_type
         self._sections = self._dict()
         self._defaults = self._dict()
+        self._views = self._dict()
+        self._views[DEFAULTSECT] = SectionProxy(self, DEFAULTSECT)
         if defaults:
             for key, value in defaults.items():
                 self._defaults[self.optionxform(key)] = value
@@ -434,6 +433,7 @@
             self._startonly_comment_prefixes = ()
             self._comment_prefixes = tuple(comment_prefixes or ())
         self._strict = strict
+        self._allow_no_value = allow_no_value
         self._empty_lines_in_values = empty_lines_in_values
 
     def defaults(self):
@@ -451,12 +451,13 @@
         already exists. Raise ValueError if name is DEFAULT or any of it's
         case-insensitive variants.
         """
-        if section.lower() == "default":
+        if section.upper() == DEFAULTSECT:
             raise ValueError('Invalid section name: %s' % section)
 
         if section in self._sections:
             raise DuplicateSectionError(section)
         self._sections[section] = self._dict()
+        self._views[section] = SectionProxy(self, section)
 
     def has_section(self, section):
         """Indicate whether the named section is present in the configuration.
@@ -534,7 +535,7 @@
         for section, keys in dictionary.items():
             try:
                 self.add_section(section)
-            except DuplicateSectionError:
+            except (DuplicateSectionError, ValueError):
                 if self._strict and section in elements_added:
                     raise
                 elements_added.add(section)
@@ -556,29 +557,31 @@
         )
         self.read_file(fp, source=filename)
 
-    def get(self, section, option, vars=None, default=_UNSET):
+    def get(self, section, option, *, vars=None, fallback=_UNSET):
         """Get an option value for a given section.
 
         If `vars' is provided, it must be a dictionary. The option is looked up
         in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
-        If the key is not found and `default' is provided, it is used as
-        a fallback value. `None' can be provided as a `default' value.
+        If the key is not found and `fallback' is provided, it is used as
+        a fallback value. `None' can be provided as a `fallback' value.
+
+        Arguments `vars' and `fallback' are keyword only.
         """
         try:
             d = self._unify_values(section, vars)
         except NoSectionError:
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise
             else:
-                return default
+                return fallback
         option = self.optionxform(option)
         try:
             return d[option]
         except KeyError:
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise NoOptionError(option, section)
             else:
-                return default
+                return fallback
 
     def items(self, section):
         try:
@@ -593,35 +596,36 @@
             del d["__name__"]
         return d.items()
 
-    def _get(self, section, conv, option, *args, **kwargs):
-        return conv(self.get(section, option, *args, **kwargs))
+    def _get(self, section, conv, option, **kwargs):
+        return conv(self.get(section, option, **kwargs))
 
-    def getint(self, section, option, vars=None, default=_UNSET):
+    def getint(self, section, option, *, vars=None, fallback=_UNSET):
         try:
-            return self._get(section, int, option, vars)
+            return self._get(section, int, option, vars=vars)
         except (NoSectionError, NoOptionError):
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise
             else:
-                return default
+                return fallback
 
-    def getfloat(self, section, option, vars=None, default=_UNSET):
+    def getfloat(self, section, option, *, vars=None, fallback=_UNSET):
         try:
-            return self._get(section, float, option, vars)
+            return self._get(section, float, option, vars=vars)
         except (NoSectionError, NoOptionError):
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise
             else:
-                return default
+                return fallback
 
-    def getboolean(self, section, option, vars=None, default=_UNSET):
+    def getboolean(self, section, option, *, vars=None, fallback=_UNSET):
         try:
-            return self._get(section, self._convert_to_boolean, option, vars)
+            return self._get(section, self._convert_to_boolean, option,
+                             vars=vars)
         except (NoSectionError, NoOptionError):
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise
             else:
-                return default
+                return fallback
 
     def optionxform(self, optionstr):
         return optionstr.lower()
@@ -671,7 +675,7 @@
         for key, value in section_items:
             if key == "__name__":
                 continue
-            if (value is not None) or (self._optcre == self.OPTCRE):
+            if value is not None or not self._allow_no_value:
                 value = delimiter + str(value).replace('\n', '\n\t')
             else:
                 value = ""
@@ -698,8 +702,40 @@
         existed = section in self._sections
         if existed:
             del self._sections[section]
+            del self._views[section]
         return existed
 
+    def __getitem__(self, key):
+        if key != DEFAULTSECT and not self.has_section(key):
+            raise KeyError(key)
+        return self._views[key]
+
+    def __setitem__(self, key, value):
+        # To conform with the mapping protocol, overwrites existing values in
+        # the section.
+
+        # XXX this is not atomic if read_dict fails at any point. Then again,
+        # no update method in configparser is atomic in this implementation.
+        self.remove_section(key)
+        self.read_dict({key: value})
+
+    def __delitem__(self, key):
+        if key == DEFAULTSECT:
+            raise ValueError("Cannot remove the default section.")
+        if not self.has_section(key):
+            raise KeyError(key)
+        self.remove_section(key)
+
+    def __contains__(self, key):
+        return key == DEFAULTSECT or self.has_section(key)
+
+    def __len__(self):
+        return len(self._sections) + 1 # the default section
+
+    def __iter__(self):
+        # XXX does it break when underlying container state changed?
+        return itertools.chain((DEFAULTSECT,), self._sections.keys())
+
     def _read(self, fp, fpname):
         """Parse a sectioned configuration file.
 
@@ -776,6 +812,7 @@
                         cursect = self._dict()
                         cursect['__name__'] = sectname
                         self._sections[sectname] = cursect
+                        self._views[sectname] = SectionProxy(self, sectname)
                         elements_added.add(sectname)
                     # So sections can't start with a continuation line
                     optname = None
@@ -818,8 +855,8 @@
         self._join_multiline_values()
 
     def _join_multiline_values(self):
-        all_sections = [self._defaults]
-        all_sections.extend(self._sections.values())
+        all_sections = itertools.chain((self._defaults,),
+                                       self._sections.values())
         for options in all_sections:
             for name, val in options.items():
                 if isinstance(val, list):
@@ -857,73 +894,95 @@
             raise ValueError('Not a boolean: %s' % value)
         return self.BOOLEAN_STATES[value.lower()]
 
+    def _validate_value_type(self, value):
+        """Raises a TypeError for non-string values.
+
+        The only legal non-string value if we allow valueless
+        options is None, so we need to check if the value is a
+        string if:
+        - we do not allow valueless options, or
+        - we allow valueless options but the value is not None
+
+        For compatibility reasons this method is not used in classic set()
+        for RawConfigParsers and ConfigParsers. It is invoked in every
+        case for mapping protocol access and in SafeConfigParser.set().
+        """
+        if not self._allow_no_value or value:
+            if not isinstance(value, str):
+                raise TypeError("option values must be strings")
+
+
 
 class ConfigParser(RawConfigParser):
     """ConfigParser implementing interpolation."""
 
-    def get(self, section, option, raw=False, vars=None, default=_UNSET):
+    def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
         """Get an option value for a given section.
 
         If `vars' is provided, it must be a dictionary. The option is looked up
         in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
-        If the key is not found and `default' is provided, it is used as
-        a fallback value. `None' can be provided as a `default' value.
+        If the key is not found and `fallback' is provided, it is used as
+        a fallback value. `None' can be provided as a `fallback' value.
 
         All % interpolations are expanded in the return values, unless the
         optional argument `raw' is true.  Values for interpolation keys are
         looked up in the same manner as the option.
 
+        Arguments `raw', `vars', and `fallback' are keyword only.
+
         The section DEFAULT is special.
         """
         try:
             d = self._unify_values(section, vars)
         except NoSectionError:
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise
             else:
-                return default
+                return fallback
         option = self.optionxform(option)
         try:
             value = d[option]
         except KeyError:
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise NoOptionError(option, section)
             else:
-                return default
+                return fallback
 
         if raw or value is None:
             return value
         else:
             return self._interpolate(section, option, value, d)
 
-    def getint(self, section, option, raw=False, vars=None, default=_UNSET):
+    def getint(self, section, option, *, raw=False, vars=None,
+               fallback=_UNSET):
         try:
-            return self._get(section, int, option, raw, vars)
+            return self._get(section, int, option, raw=raw, vars=vars)
         except (NoSectionError, NoOptionError):
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise
             else:
-                return default
+                return fallback
 
-    def getfloat(self, section, option, raw=False, vars=None, default=_UNSET):
+    def getfloat(self, section, option, *, raw=False, vars=None,
+                 fallback=_UNSET):
         try:
-            return self._get(section, float, option, raw, vars)
+            return self._get(section, float, option, raw=raw, vars=vars)
         except (NoSectionError, NoOptionError):
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise
             else:
-                return default
+                return fallback
 
-    def getboolean(self, section, option, raw=False, vars=None,
-                   default=_UNSET):
+    def getboolean(self, section, option, *, raw=False, vars=None,
+                   fallback=_UNSET):
         try:
-            return self._get(section, self._convert_to_boolean, option, raw,
-                             vars)
+            return self._get(section, self._convert_to_boolean, option,
+                             raw=raw, vars=vars)
         except (NoSectionError, NoOptionError):
-            if default is _UNSET:
+            if fallback is _UNSET:
                 raise
             else:
-                return default
+                return fallback
 
     def items(self, section, raw=False, vars=None):
         """Return a list of (name, value) tuples for each option in a section.
@@ -1037,14 +1096,7 @@
 
     def set(self, section, option, value=None):
         """Set an option.  Extend ConfigParser.set: check for string values."""
-        # The only legal non-string value if we allow valueless
-        # options is None, so we need to check if the value is a
-        # string if:
-        # - we do not allow valueless options, or
-        # - we allow valueless options but the value is not None
-        if self._optcre is self.OPTCRE or value:
-            if not isinstance(value, str):
-                raise TypeError("option values must be strings")
+        self._validate_value_type(value)
         # check for bad percent signs
         if value:
             tmp_value = value.replace('%%', '') # escaped percent signs
@@ -1053,3 +1105,60 @@
                 raise ValueError("invalid interpolation syntax in %r at "
                                 "position %d" % (value, tmp_value.find('%')))
         ConfigParser.set(self, section, option, value)
+
+
+class SectionProxy(MutableMapping):
+    """A proxy for a single section from a parser."""
+
+    _noname = ("__name__ special key access and modification "
+               "not supported through the mapping interface.")
+
+    def __init__(self, parser, section_name):
+        """Creates a view on a section named `section_name` in `parser`."""
+        self._parser = parser
+        self._section = section_name
+        self.getint = functools.partial(self._parser.getint,
+                                        self._section)
+        self.getfloat = functools.partial(self._parser.getfloat,
+                                          self._section)
+        self.getboolean = functools.partial(self._parser.getboolean,
+                                            self._section)
+
+    def __repr__(self):
+        return '<Section: {}>'.format(self._section)
+
+    def __getitem__(self, key):
+        if key == '__name__':
+            raise ValueError(self._noname)
+        if not self._parser.has_option(self._section, key):
+            raise KeyError(key)
+        return self._parser.get(self._section, key)
+
+    def __setitem__(self, key, value):
+        if key == '__name__':
+            raise ValueError(self._noname)
+        self._parser._validate_value_type(value)
+        return self._parser.set(self._section, key, value)
+
+    def __delitem__(self, key):
+        if key == '__name__':
+            raise ValueError(self._noname)
+        if not self._parser.has_option(self._section, key):
+            raise KeyError(key)
+        return self._parser.remove_option(self._section, key)
+
+    def __contains__(self, key):
+        if key == '__name__':
+            return False
+        return self._parser.has_option(self._section, key)
+
+    def __len__(self):
+        # __name__ is properly hidden by .options()
+        # XXX weak performance
+        return len(self._parser.options(self._section))
+
+    def __iter__(self):
+        # __name__ is properly hidden by .options()
+        # XXX weak performance
+        # XXX does not break when underlying container state changed
+        return self._parser.options(self._section).__iter__()

Modified: python/branches/py3k/Lib/logging/config.py
==============================================================================
--- python/branches/py3k/Lib/logging/config.py	(original)
+++ python/branches/py3k/Lib/logging/config.py	Wed Nov 10 19:57:39 2010
@@ -103,7 +103,7 @@
 
 def _create_formatters(cp):
     """Create and return formatters"""
-    flist = cp.get("formatters", "keys")
+    flist = cp["formatters"]["keys"]
     if not len(flist):
         return {}
     flist = flist.split(",")
@@ -111,20 +111,12 @@
     formatters = {}
     for form in flist:
         sectname = "formatter_%s" % form
-        opts = cp.options(sectname)
-        if "format" in opts:
-            fs = cp.get(sectname, "format", 1)
-        else:
-            fs = None
-        if "datefmt" in opts:
-            dfs = cp.get(sectname, "datefmt", 1)
-        else:
-            dfs = None
+        fs = cp.get(sectname, "format", raw=True, fallback=None)
+        dfs = cp.get(sectname, "datefmt", raw=True, fallback=None)
         c = logging.Formatter
-        if "class" in opts:
-            class_name = cp.get(sectname, "class")
-            if class_name:
-                c = _resolve(class_name)
+        class_name = cp[sectname].get("class")
+        if class_name:
+            c = _resolve(class_name)
         f = c(fs, dfs)
         formatters[form] = f
     return formatters
@@ -132,7 +124,7 @@
 
 def _install_handlers(cp, formatters):
     """Install and return handlers"""
-    hlist = cp.get("handlers", "keys")
+    hlist = cp["handlers"]["keys"]
     if not len(hlist):
         return {}
     hlist = hlist.split(",")
@@ -140,30 +132,23 @@
     handlers = {}
     fixups = [] #for inter-handler references
     for hand in hlist:
-        sectname = "handler_%s" % hand
-        klass = cp.get(sectname, "class")
-        opts = cp.options(sectname)
-        if "formatter" in opts:
-            fmt = cp.get(sectname, "formatter")
-        else:
-            fmt = ""
+        section = cp["handler_%s" % hand]
+        klass = section["class"]
+        fmt = section.get("formatter", "")
         try:
             klass = eval(klass, vars(logging))
         except (AttributeError, NameError):
             klass = _resolve(klass)
-        args = cp.get(sectname, "args")
+        args = section["args"]
         args = eval(args, vars(logging))
         h = klass(*args)
-        if "level" in opts:
-            level = cp.get(sectname, "level")
+        if "level" in section:
+            level = section["level"]
             h.setLevel(logging._levelNames[level])
         if len(fmt):
             h.setFormatter(formatters[fmt])
         if issubclass(klass, logging.handlers.MemoryHandler):
-            if "target" in opts:
-                target = cp.get(sectname,"target")
-            else:
-                target = ""
+            target = section.get("target", "")
             if len(target): #the target handler may not be loaded yet, so keep for later...
                 fixups.append((h, target))
         handlers[hand] = h
@@ -197,20 +182,19 @@
     """Create and install loggers"""
 
     # configure the root first
-    llist = cp.get("loggers", "keys")
+    llist = cp["loggers"]["keys"]
     llist = llist.split(",")
     llist = list(map(lambda x: x.strip(), llist))
     llist.remove("root")
-    sectname = "logger_root"
+    section = cp["logger_root"]
     root = logging.root
     log = root
-    opts = cp.options(sectname)
-    if "level" in opts:
-        level = cp.get(sectname, "level")
+    if "level" in section:
+        level = section["level"]
         log.setLevel(logging._levelNames[level])
     for h in root.handlers[:]:
         root.removeHandler(h)
-    hlist = cp.get(sectname, "handlers")
+    hlist = section["handlers"]
     if len(hlist):
         hlist = hlist.split(",")
         hlist = _strip_spaces(hlist)
@@ -237,13 +221,9 @@
     child_loggers = []
     #now set up the new ones...
     for log in llist:
-        sectname = "logger_%s" % log
-        qn = cp.get(sectname, "qualname")
-        opts = cp.options(sectname)
-        if "propagate" in opts:
-            propagate = cp.getint(sectname, "propagate")
-        else:
-            propagate = 1
+        section = cp["logger_%s" % log]
+        qn = section["qualname"]
+        propagate = section.getint("propagate", fallback=1)
         logger = logging.getLogger(qn)
         if qn in existing:
             i = existing.index(qn)
@@ -255,14 +235,14 @@
                 child_loggers.append(existing[i])
                 i = i + 1
             existing.remove(qn)
-        if "level" in opts:
-            level = cp.get(sectname, "level")
+        if "level" in section:
+            level = section["level"]
             logger.setLevel(logging._levelNames[level])
         for h in logger.handlers[:]:
             logger.removeHandler(h)
         logger.propagate = propagate
         logger.disabled = 0
-        hlist = cp.get(sectname, "handlers")
+        hlist = section["handlers"]
         if len(hlist):
             hlist = hlist.split(",")
             hlist = _strip_spaces(hlist)

Modified: python/branches/py3k/Lib/test/test_cfgparser.py
==============================================================================
--- python/branches/py3k/Lib/test/test_cfgparser.py	(original)
+++ python/branches/py3k/Lib/test/test_cfgparser.py	Wed Nov 10 19:57:39 2010
@@ -52,8 +52,6 @@
 class BasicTestCase(CfgParserTestCaseClass):
 
     def basic_test(self, cf):
-        L = cf.sections()
-        L.sort()
         E = ['Commented Bar',
              'Foo Bar',
              'Internationalized Stuff',
@@ -64,20 +62,34 @@
              'Spacey Bar From The Beginning',
              'Types',
              ]
+
         if self.allow_no_value:
             E.append('NoValue')
         E.sort()
+
+        # API access
+        L = cf.sections()
+        L.sort()
         eq = self.assertEqual
         eq(L, E)
 
+        # mapping access
+        L = [section for section in cf]
+        L.sort()
+        E.append(configparser.DEFAULTSECT)
+        E.sort()
+        eq(L, E)
+
         # The use of spaces in the section names serves as a
         # regression test for SourceForge bug #583248:
         # http://www.python.org/sf/583248
-        eq(cf.get('Foo Bar', 'foo'), 'bar')
-        eq(cf.get('Spacey Bar', 'foo'), 'bar')
-        eq(cf.get('Spacey Bar From The Beginning', 'foo'), 'bar')
+
+        # API access
+        eq(cf.get('Foo Bar', 'foo'), 'bar1')
+        eq(cf.get('Spacey Bar', 'foo'), 'bar2')
+        eq(cf.get('Spacey Bar From The Beginning', 'foo'), 'bar3')
         eq(cf.get('Spacey Bar From The Beginning', 'baz'), 'qwe')
-        eq(cf.get('Commented Bar', 'foo'), 'bar')
+        eq(cf.get('Commented Bar', 'foo'), 'bar4')
         eq(cf.get('Commented Bar', 'baz'), 'qwe')
         eq(cf.get('Spaces', 'key with spaces'), 'value')
         eq(cf.get('Spaces', 'another with spaces'), 'splat!')
@@ -89,40 +101,69 @@
         if self.allow_no_value:
             eq(cf.get('NoValue', 'option-without-value'), None)
 
-        # test vars= and default=
-        eq(cf.get('Foo Bar', 'foo', default='baz'), 'bar')
+        # test vars= and fallback=
+        eq(cf.get('Foo Bar', 'foo', fallback='baz'), 'bar1')
         eq(cf.get('Foo Bar', 'foo', vars={'foo': 'baz'}), 'baz')
         with self.assertRaises(configparser.NoSectionError):
             cf.get('No Such Foo Bar', 'foo')
         with self.assertRaises(configparser.NoOptionError):
             cf.get('Foo Bar', 'no-such-foo')
-        eq(cf.get('No Such Foo Bar', 'foo', default='baz'), 'baz')
-        eq(cf.get('Foo Bar', 'no-such-foo', default='baz'), 'baz')
-        eq(cf.get('Spacey Bar', 'foo', default=None), 'bar')
-        eq(cf.get('No Such Spacey Bar', 'foo', default=None), None)
-        eq(cf.getint('Types', 'int', default=18), 42)
-        eq(cf.getint('Types', 'no-such-int', default=18), 18)
-        eq(cf.getint('Types', 'no-such-int', default="18"), "18") # sic!
+        eq(cf.get('No Such Foo Bar', 'foo', fallback='baz'), 'baz')
+        eq(cf.get('Foo Bar', 'no-such-foo', fallback='baz'), 'baz')
+        eq(cf.get('Spacey Bar', 'foo', fallback=None), 'bar2')
+        eq(cf.get('No Such Spacey Bar', 'foo', fallback=None), None)
+        eq(cf.getint('Types', 'int', fallback=18), 42)
+        eq(cf.getint('Types', 'no-such-int', fallback=18), 18)
+        eq(cf.getint('Types', 'no-such-int', fallback="18"), "18") # sic!
         self.assertAlmostEqual(cf.getfloat('Types', 'float',
-                                           default=0.0), 0.44)
+                                           fallback=0.0), 0.44)
         self.assertAlmostEqual(cf.getfloat('Types', 'no-such-float',
-                                           default=0.0), 0.0)
-        eq(cf.getfloat('Types', 'no-such-float', default="0.0"), "0.0") # sic!
-        eq(cf.getboolean('Types', 'boolean', default=True), False)
-        eq(cf.getboolean('Types', 'no-such-boolean', default="yes"),
+                                           fallback=0.0), 0.0)
+        eq(cf.getfloat('Types', 'no-such-float', fallback="0.0"), "0.0") # sic!
+        eq(cf.getboolean('Types', 'boolean', fallback=True), False)
+        eq(cf.getboolean('Types', 'no-such-boolean', fallback="yes"),
            "yes") # sic!
-        eq(cf.getboolean('Types', 'no-such-boolean', default=True), True)
-        eq(cf.getboolean('No Such Types', 'boolean', default=True), True)
+        eq(cf.getboolean('Types', 'no-such-boolean', fallback=True), True)
+        eq(cf.getboolean('No Such Types', 'boolean', fallback=True), True)
         if self.allow_no_value:
-            eq(cf.get('NoValue', 'option-without-value', default=False), None)
+            eq(cf.get('NoValue', 'option-without-value', fallback=False), None)
             eq(cf.get('NoValue', 'no-such-option-without-value',
-                      default=False), False)
+                      fallback=False), False)
 
+        # mapping access
+        eq(cf['Foo Bar']['foo'], 'bar1')
+        eq(cf['Spacey Bar']['foo'], 'bar2')
+        eq(cf['Spacey Bar From The Beginning']['foo'], 'bar3')
+        eq(cf['Spacey Bar From The Beginning']['baz'], 'qwe')
+        eq(cf['Commented Bar']['foo'], 'bar4')
+        eq(cf['Commented Bar']['baz'], 'qwe')
+        eq(cf['Spaces']['key with spaces'], 'value')
+        eq(cf['Spaces']['another with spaces'], 'splat!')
+        eq(cf['Long Line']['foo'],
+           'this line is much, much longer than my editor\nlikes it.')
+        if self.allow_no_value:
+            eq(cf['NoValue']['option-without-value'], None)
+
+        # API access
         self.assertNotIn('__name__', cf.options("Foo Bar"),
                          '__name__ "option" should not be exposed by the API!')
 
+        # mapping access
+        self.assertNotIn('__name__', cf['Foo Bar'],
+                         '__name__ "option" should not be exposed by '
+                         'mapping protocol access')
+        self.assertFalse('__name__' in cf['Foo Bar'])
+        with self.assertRaises(ValueError):
+            cf['Foo Bar']['__name__']
+        with self.assertRaises(ValueError):
+            del cf['Foo Bar']['__name__']
+        with self.assertRaises(ValueError):
+            cf['Foo Bar']['__name__'] = "can't write to this special name"
+
         # Make sure the right things happen for remove_option();
         # added to include check for SourceForge bug #123324:
+
+        # API acceess
         self.assertTrue(cf.remove_option('Foo Bar', 'foo'),
                         "remove_option() failed to report existence of option")
         self.assertFalse(cf.has_option('Foo Bar', 'foo'),
@@ -138,17 +179,25 @@
         eq(cf.get('Long Line', 'foo'),
            'this line is much, much longer than my editor\nlikes it.')
 
+        # mapping access
+        del cf['Spacey Bar']['foo']
+        self.assertFalse('foo' in cf['Spacey Bar'])
+        with self.assertRaises(KeyError):
+            del cf['Spacey Bar']['foo']
+        with self.assertRaises(KeyError):
+            del cf['No Such Section']['foo']
+
     def test_basic(self):
         config_string = """\
 [Foo Bar]
-foo{0[0]}bar
+foo{0[0]}bar1
 [Spacey Bar]
-foo {0[0]} bar
+foo {0[0]} bar2
 [Spacey Bar From The Beginning]
-  foo {0[0]} bar
+  foo {0[0]} bar3
   baz {0[0]} qwe
 [Commented Bar]
-foo{0[1]} bar {1[1]} comment
+foo{0[1]} bar4 {1[1]} comment
 baz{0[0]}qwe {1[0]}another one
 [Long Line]
 foo{0[1]} this line is much, much longer than my editor
@@ -205,17 +254,17 @@
     def test_basic_from_dict(self):
         config = {
             "Foo Bar": {
-                "foo": "bar",
+                "foo": "bar1",
             },
             "Spacey Bar": {
-                "foo": "bar",
+                "foo": "bar2",
             },
             "Spacey Bar From The Beginning": {
-                "foo": "bar",
+                "foo": "bar3",
                 "baz": "qwe",
             },
             "Commented Bar": {
-                "foo": "bar",
+                "foo": "bar4",
                 "baz": "qwe",
             },
             "Long Line": {
@@ -270,14 +319,18 @@
         cf = self.newconfig()
         cf.add_section("A")
         cf.add_section("a")
+        cf.add_section("B")
         L = cf.sections()
         L.sort()
         eq = self.assertEqual
-        eq(L, ["A", "a"])
+        eq(L, ["A", "B", "a"])
         cf.set("a", "B", "value")
         eq(cf.options("a"), ["b"])
         eq(cf.get("a", "b"), "value",
            "could not locate option, expecting case-insensitive option names")
+        with self.assertRaises(configparser.NoSectionError):
+            # section names are case-sensitive
+            cf.set("b", "A", "value")
         self.assertTrue(cf.has_option("a", "b"))
         cf.set("A", "A-B", "A-B value")
         for opt in ("a-b", "A-b", "a-B", "A-B"):
@@ -291,7 +344,7 @@
 
         # SF bug #432369:
         cf = self.fromstring(
-            "[MySection]\nOption{} first line\n\tsecond line\n".format(
+            "[MySection]\nOption{} first line   \n\tsecond line   \n".format(
                 self.delimiters[0]))
         eq(cf.options("MySection"), ["option"])
         eq(cf.get("MySection", "Option"), "first line\nsecond line")
@@ -303,6 +356,46 @@
         self.assertTrue(cf.has_option("section", "Key"))
 
 
+    def test_case_sensitivity_mapping_access(self):
+        cf = self.newconfig()
+        cf["A"] = {}
+        cf["a"] = {"B": "value"}
+        cf["B"] = {}
+        L = [section for section in cf]
+        L.sort()
+        eq = self.assertEqual
+        elem_eq = self.assertItemsEqual
+        eq(L, ["A", "B", configparser.DEFAULTSECT, "a"])
+        eq(cf["a"].keys(), {"b"})
+        eq(cf["a"]["b"], "value",
+           "could not locate option, expecting case-insensitive option names")
+        with self.assertRaises(KeyError):
+            # section names are case-sensitive
+            cf["b"]["A"] = "value"
+        self.assertTrue("b" in cf["a"])
+        cf["A"]["A-B"] = "A-B value"
+        for opt in ("a-b", "A-b", "a-B", "A-B"):
+            self.assertTrue(
+                opt in cf["A"],
+                "has_option() returned false for option which should exist")
+        eq(cf["A"].keys(), {"a-b"})
+        eq(cf["a"].keys(), {"b"})
+        del cf["a"]["B"]
+        elem_eq(cf["a"].keys(), {})
+
+        # SF bug #432369:
+        cf = self.fromstring(
+            "[MySection]\nOption{} first line   \n\tsecond line   \n".format(
+                self.delimiters[0]))
+        eq(cf["MySection"].keys(), {"option"})
+        eq(cf["MySection"]["Option"], "first line\nsecond line")
+
+        # SF bug #561822:
+        cf = self.fromstring("[section]\n"
+                             "nekey{}nevalue\n".format(self.delimiters[0]),
+                             defaults={"key":"value"})
+        self.assertTrue("Key" in cf["section"])
+
     def test_default_case_sensitivity(self):
         cf = self.newconfig({"foo": "Bar"})
         self.assertEqual(


More information about the Python-checkins mailing list