[Python-checkins] r86976 - in python/branches/py3k: Doc/library/configparser.rst Doc/library/fileformats.rst Lib/configparser.py Lib/test/test_cfgparser.py Misc/NEWS

lukasz.langa python-checkins at python.org
Fri Dec 3 17:28:00 CET 2010


Author: lukasz.langa
Date: Fri Dec  3 17:28:00 2010
New Revision: 86976

Log:
Issue 10499: Modular interpolation in configparser


Modified:
   python/branches/py3k/Doc/library/configparser.rst
   python/branches/py3k/Doc/library/fileformats.rst
   python/branches/py3k/Lib/configparser.py
   python/branches/py3k/Lib/test/test_cfgparser.py
   python/branches/py3k/Misc/NEWS

Modified: python/branches/py3k/Doc/library/configparser.rst
==============================================================================
--- python/branches/py3k/Doc/library/configparser.rst	(original)
+++ python/branches/py3k/Doc/library/configparser.rst	Fri Dec  3 17:28:00 2010
@@ -17,11 +17,10 @@
    single: ini file
    single: Windows ini file
 
-This module provides the classes :class:`RawConfigParser` and
-:class:`SafeConfigParser`.  They implement a basic configuration
-language which provides a structure similar to what's found in Microsoft
-Windows INI files.  You can use this to write Python programs which can be
-customized by end users easily.
+This module provides the :class:`SafeConfigParser` class which implements
+a basic configuration language which provides a structure similar to what's
+found in Microsoft Windows INI files.  You can use this to write Python
+programs which can be customized by end users easily.
 
 .. note::
 
@@ -34,6 +33,10 @@
       Support for a creating Unix shell-like mini-languages which can be used
       as an alternate format for application configuration files.
 
+   Module :mod:`json`
+      The json module implements a subset of JavaScript syntax which can also
+      be used for this purpose.
+
 
 Quick Start
 -----------
@@ -43,17 +46,17 @@
 .. code-block:: ini
 
    [DEFAULT]
-     ServerAliveInterval = 45
-     Compression = yes
-     CompressionLevel = 9
-     ForwardX11 = yes
+   ServerAliveInterval = 45
+   Compression = yes
+   CompressionLevel = 9
+   ForwardX11 = yes
 
    [bitbucket.org]
-     User = hg
+   User = hg
 
    [topsecret.server.com]
-     Port = 50022
-     ForwardX11 = no
+   Port = 50022
+   ForwardX11 = no
 
 The structure of INI files is described `in the following section
 <#supported-ini-file-structure>`_.  Essentially, the file
@@ -64,7 +67,7 @@
 .. doctest::
 
    >>> import configparser
-   >>> config = configparser.RawConfigParser()
+   >>> config = configparser.SafeConfigParser()
    >>> config['DEFAULT'] = {'ServerAliveInterval': '45',
    ...                      'Compression': 'yes',
    ...                      'CompressionLevel': '9'}
@@ -89,7 +92,7 @@
 .. doctest::
 
    >>> import configparser
-   >>> config = configparser.RawConfigParser()
+   >>> config = configparser.SafeConfigParser()
    >>> config.sections()
    []
    >>> config.read('example.ini')
@@ -233,23 +236,26 @@
 compatibility, by default only ``;`` starts an inline comment, while
 ``#`` does not [1]_.
 
-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
-[1]_.  Additional defaults can be provided on initialization.
-
 For example:
 
 .. code-block:: ini
 
-   [Paths]
-   home_dir: /Users
-   my_dir: %(home_dir)s/lumberjack
-   my_pictures: %(my_dir)s/Pictures
+   [Simple Values]
+   key: value
+   spaces in keys: allowed
+   spaces in values: allowed as well
+   you can also use = to delimit keys from values
+
+   [All Values Are Strings]
+   values like this: 1000000
+   or this: 3.14159265359
+   are they treated as numbers? : no
+   integers, floats and booleans are held as: strings
+   can use the API to get converted values directly: true
 
    [Multiline Values]
    chorus: I'm a lumberjack, and I'm okay
-      I sleep all night and I work all day
+       I sleep all night and I work all day
 
    [No Values]
    key_without_value
@@ -262,28 +268,92 @@
            multiline ;comment
            value!    ;comment
 
-      [Sections Can Be Indented]
-         can_values_be_as_well = True
-         does_that_mean_anything_special = False
-         purpose = formatting for readability
-         multiline_values = are
-            handled just fine as
-            long as they are indented
-            deeper than the first line
-            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
-demand so keys used in the chain of references do not have to be specified in
-any specific order in the configuration file.
-
-:class:`RawConfigParser` would simply return ``%(my_dir)s/Pictures`` as the
-value of ``my_pictures`` and ``%(home_dir)s/lumberjack`` as the value of
-``my_dir``.  Other features presented in the example are handled in the same
-manner by both parsers.
+       [Sections Can Be Indented]
+           can_values_be_as_well = True
+           does_that_mean_anything_special = False
+           purpose = formatting for readability
+           multiline_values = are
+               handled just fine as
+               long as they are indented
+               deeper than the first line
+               of a value
+           # Did I mention we can indent comments, too?
+
+
+Interpolation of values
+-----------------------
+
+On top of the core functionality, :class:`SafeConfigParser` supports
+interpolation.  This means values can be preprocessed before returning them
+from ``get()`` calls.
+
+.. class:: BasicInterpolation()
+
+   The default implementation used by :class:`SafeConfigParser`.  It enables
+   values to contain format strings which refer to other values in the same
+   section, or values in the special default section [1]_.  Additional default
+   values can be provided on initialization.
+
+   For example:
+
+   .. code-block:: ini
+
+      [Paths]
+      home_dir: /Users
+      my_dir: %(home_dir)s/lumberjack
+      my_pictures: %(my_dir)s/Pictures
+
+
+   In the example above, :class:`SafeConfigParser` with *interpolation* set to
+   ``BasicInterpolation()`` 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 demand so
+   keys used in the chain of references do not have to be specified in any
+   specific order in the configuration file.
+
+   With ``interpolation`` set to ``None``, the parser would simply return
+   ``%(my_dir)s/Pictures`` as the value of ``my_pictures`` and
+   ``%(home_dir)s/lumberjack`` as the value of ``my_dir``.
+
+.. class:: ExtendedInterpolation()
+
+   An alternative handler for interpolation which implements a more advanced
+   syntax, used for instance in ``zc.buildout``. Extended interpolation is
+   using ``${section:option}`` to denote a value from a foreign section.
+   Interpolation can span multiple levels. For convenience, if the ``section:``
+   part is omitted, interpolation defaults to the current section (and possibly
+   the default values from the special section).
+
+   For example, the configuration specified above with basic interpolation,
+   would look like this with extended interpolation:
+
+   .. code-block:: ini
+
+      [Paths]
+      home_dir: /Users
+      my_dir: ${home_dir}/lumberjack
+      my_pictures: ${my_dir}/Pictures
+
+   Values from other sections can be fetched as well:
 
+   .. code-block:: ini
+
+      [Common]
+      home_dir: /Users
+      library_dir: /Library
+      system_dir: /System
+      macports_dir: /opt/local
+
+      [Frameworks]
+      Python: 3.2
+      path: ${Common:system_dir}/Library/Frameworks/
+
+      [Arthur]
+      nickname: Two Sheds
+      last_name: Jackson
+      my_dir: ${Common:home_dir}/twosheds
+      my_pictures: ${my_dir}/Pictures
+      python_dir: ${Frameworks:path}/Python/Versions/${Frameworks:Python}
 
 Mapping Protocol Access
 -----------------------
@@ -350,9 +420,9 @@
 * *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.
+  put in the ``DEFAULT`` section.  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
   :meth:`read_dict` before you read the actual file.
@@ -374,7 +444,7 @@
 
   .. doctest::
 
-     >>> parser = configparser.RawConfigParser()
+     >>> parser = configparser.SafeConfigParser()
      >>> parser.read_dict({'section1': {'key1': 'value1',
      ...                                'key2': 'value2',
      ...                                'key3': 'value3'},
@@ -395,7 +465,7 @@
   .. doctest::
 
      >>> from collections import OrderedDict
-     >>> parser = configparser.RawConfigParser()
+     >>> parser = configparser.SafeConfigParser()
      >>> parser.read_dict(
      ...   OrderedDict((
      ...     ('s1',
@@ -441,7 +511,7 @@
      ...   skip-bdb
      ...   skip-innodb # we don't need ACID today
      ... """
-     >>> config = configparser.RawConfigParser(allow_no_value=True)
+     >>> config = configparser.SafeConfigParser(allow_no_value=True)
      >>> config.read_string(sample_config)
 
      >>> # Settings with values are treated as before:
@@ -464,7 +534,7 @@
   This means values (but not keys) can contain the delimiters.
 
   See also the *space_around_delimiters* argument to
-  :meth:`RawConfigParser.write`.
+  :meth:`SafeConfigParser.write`.
 
 * *comment_prefixes*, default value: ``_COMPATIBLE`` (``'#'`` valid on empty
   lines, ``';'`` valid also on non-empty lines)
@@ -512,6 +582,31 @@
   will make empty lines split keys every time.  In the example above, it would
   produce two keys, ``key`` and ``this``.
 
+* *default_section*, default value: ``configparser.DEFAULTSECT`` (that is:
+  ``"DEFAULT"``)
+
+  The convention of allowing a special section of default values for other
+  sections or interpolation purposes is a powerful concept of this library,
+  letting users create complex declarative configurations. This section is
+  normally called ``"DEFAULT"`` but this can be customized to point to any
+  other valid section name. Some typical values include: ``"general"`` or
+  ``"common"``. The name provided is used for recognizing default sections when
+  reading from any source and is used when writing configuration back to
+  a file. Its current value can be retrieved using the
+  ``parser_instance.default_section`` attribute and may be modified at runtime
+  (i.e. to convert files from one format to another).
+
+* *interpolation*, default value: ``configparser.BasicInterpolation``
+
+  Interpolation behaviour may be customized by providing a custom handler
+  through the *interpolation* argument. ``None`` can be used to turn off
+  interpolation completely, ``ExtendedInterpolation()`` provides a more
+  advanced variant inspired by ``zc.buildout``. More on the subject in the
+  `dedicated documentation section <#interpolation-of-values>`_.
+
+  .. note:: :class:`RawConfigParser` is using ``None`` by default and
+     :class:`ConfigParser` is using ``configparser.BrokenInterpolation``.
+
 
 More advanced customization may be achieved by overriding default values of
 these parser attributes.  The defaults are defined on the classes, so they
@@ -527,7 +622,7 @@
 
   .. doctest::
 
-     >>> custom = configparser.RawConfigParser()
+     >>> custom = configparser.SafeConfigParser()
      >>> custom['section1'] = {'funky': 'nope'}
      >>> custom['section1'].getboolean('funky')
      Traceback (most recent call last):
@@ -557,7 +652,7 @@
      ... [Section2]
      ... AnotherKey = Value
      ... """
-     >>> typical = configparser.RawConfigParser()
+     >>> typical = configparser.SafeConfigParser()
      >>> typical.read_string(config)
      >>> list(typical['Section1'].keys())
      ['key']
@@ -623,8 +718,7 @@
    if config.getboolean('Section1', 'bool'):
        print(config.get('Section1', 'foo'))
 
-To get interpolation, use :class:`SafeConfigParser` or, if
-you absolutely have to, a :class:`ConfigParser`::
+To get interpolation, use :class:`SafeConfigParser`::
 
    import configparser
 
@@ -672,14 +766,14 @@
    print(config.get('Section1', 'foo')) # -> "Life is hard!"
 
 
-.. _rawconfigparser-objects:
+.. _safeconfigparser-objects:
 
-RawConfigParser Objects
------------------------
+SafeConfigParser Objects
+------------------------
 
-.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
+.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True, default_section=configparser.DEFAULTSECT, interpolation=BasicInterpolation())
 
-   The basic configuration parser.  When *defaults* is given, it is initialized
+   The main configuration parser.  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.
@@ -698,16 +792,33 @@
    (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``.
+   values are accepted; the value held for these is ``None`` and they are
+   serialized without the trailing delimiter.
+
+   When *default_section* is given, it specifies the name for the special
+   section holding default values for other sections and interpolation purposes
+   (normally named ``"DEFAULT"``). This value can be retrieved and changed on
+   runtime using the ``default_section`` instance attribute.
+
+   Interpolation behaviour may be customized by providing a custom handler
+   through the *interpolation* argument. ``None`` can be used to turn off
+   interpolation completely, ``ExtendedInterpolation()`` provides a more
+   advanced variant inspired by ``zc.buildout``. More on the subject in the
+   `dedicated documentation section <#interpolation-of-values>`_.
 
-   This class does not support the magical interpolation behavior.
+   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.
+      *allow_no_value*, *delimiters*, *comment_prefixes*, *strict*,
+      *empty_lines_in_values*, *default_section* and *interpolation* were
+      added.
 
 
    .. method:: defaults()
@@ -717,22 +828,21 @@
 
    .. method:: sections()
 
-      Return a list of the sections available; ``DEFAULT`` is not included in
-      the list.
+      Return a list of the sections available; the *default section* is not
+      included in the list.
 
 
    .. method:: add_section(section)
 
       Add a section named *section* to the instance.  If a section by the given
-      name already exists, :exc:`DuplicateSectionError` is raised.  If the name
-      ``DEFAULT`` (or any of it's case-insensitive variants) is passed,
-      :exc:`ValueError` is raised.
+      name already exists, :exc:`DuplicateSectionError` is raised.  If the
+      *default section* name is passed, :exc:`ValueError` is raised.
 
 
    .. method:: has_section(section)
 
-      Indicates whether the named section is present in the configuration.  The
-      ``DEFAULT`` section is not acknowledged.
+      Indicates whether the named *section* is present in the configuration.
+      The *default section* is not acknowledged.
 
 
    .. method:: options(section)
@@ -742,7 +852,7 @@
 
    .. method:: has_option(section, option)
 
-      If the given section exists, and contains the given option, return
+      If the given *section* exists, and contains the given *option*, return
       :const:`True`; otherwise return :const:`False`.
 
 
@@ -750,19 +860,20 @@
 
       Attempt to read and parse a list of filenames, returning a list of
       filenames which were successfully parsed.  If *filenames* is a string, it
-      is treated as a single filename.  If a file named in *filenames* cannot be
-      opened, that file will be ignored.  This is designed so that you can
-      specify a list of potential configuration file locations (for example, the
-      current directory, the user's home directory, and some system-wide
-      directory), and all existing configuration files in the list will be read.
-      If none of the named files exist, the :class:`ConfigParser` instance will
-      contain an empty dataset.  An application which requires initial values to
-      be loaded from a file should load the required file or files using
-      :meth:`read_file` before calling :meth:`read` for any optional files::
+      is treated as a single filename.  If a file named in *filenames* cannot
+      be opened, that file will be ignored.  This is designed so that you can
+      specify a list of potential configuration file locations (for example,
+      the current directory, the user's home directory, and some system-wide
+      directory), and all existing configuration files in the list will be
+      read.  If none of the named files exist, the :class:`ConfigParser`
+      instance will contain an empty dataset.  An application which requires
+      initial values to be loaded from a file should load the required file or
+      files using :meth:`read_file` before calling :meth:`read` for any
+      optional files::
 
          import configparser, os
 
-         config = configparser.ConfigParser()
+         config = configparser.SafeConfigParser()
          config.read_file(open('defaults.cfg'))
          config.read(['site.cfg', os.path.expanduser('~/.myapp.cfg')],
                      encoding='cp1250')
@@ -810,7 +921,8 @@
 
       .. versionadded:: 3.2
 
-   .. method:: get(section, option, [vars, fallback])
+
+   .. method:: 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),
@@ -818,58 +930,54 @@
       and *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 *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).
+         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:: getint(section, option, [vars, fallback])
+   .. method:: 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 *vars* and *fallback*.
+      to an integer.  See :meth:`get` for explanation of *raw*, *vars* and
+      *fallback*.
 
 
-   .. method:: getfloat(section, option, [vars, fallback])
+   .. method:: 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 *vars* and
-      *fallback*.
+      to a floating point number.  See :meth:`get` for explanation of *raw*,
+      *vars* and *fallback*.
 
 
-   .. method:: getboolean(section, option, [vars, fallback])
+   .. method:: 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
-      ``"1"``, ``"yes"``, ``"true"``, and ``"on"``, which cause this method to
-      return ``True``, and ``"0"``, ``"no"``, ``"false"``, and ``"off"``, which
+      ``'1'``, ``'yes'``, ``'true'``, and ``'on'``, which cause this method to
+      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
+      :exc:`ValueError`.  See :meth:`get` for explanation of *raw*, *vars* and
       *fallback*.
 
 
-   .. method:: items(section)
+   .. method:: items(section, raw=False, vars=None)
 
-      Return a list of *name*, *value* pairs for each option in the given
-      *section*.
+      Return a list of *name*, *value* pairs for the options in the given
+      *section*.  Optional arguments have the same meaning as for the
+      :meth:`get` method.
 
 
    .. method:: 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.
-
-      .. note::
-
-         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.
+      otherwise raise :exc:`NoSectionError`.  *value* must be a string; if not,
+      :exc:`TypeError` is raised.
 
 
    .. method:: write(fileobject, space_around_delimiters=True)
@@ -921,134 +1029,61 @@
          Use :meth:`read_file` instead.
 
 
-.. _configparser-objects:
-
-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.
-
-.. 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.
-
-   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:: 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 *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:: 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
-      *fallback*.
-
-
-   .. method:: 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 *fallback*.
-
-
-   .. method:: getboolean(section, option, raw=False, [vars, fallback])
+.. data:: MAX_INTERPOLATION_DEPTH
 
-      A convenience method which coerces the *option* in the specified *section*
-      to a Boolean value.  Note that the accepted values for the option are
-      ``'1'``, ``'yes'``, ``'true'``, and ``'on'``, which cause this method to
-      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 *raw*, *vars* and
-      *fallback*.
+   The maximum depth for recursive interpolation for :meth:`get` when the *raw*
+   parameter is false.  This is relevant only when the default *interpolation*
+   is used.
 
 
-   .. method:: items(section, raw=False, vars=None)
+.. _rawconfigparser-objects:
 
-      Return a list of *name*, *value* pairs for the options in the given
-      *section*.  Optional arguments have the same meaning as for the
-      :meth:`get` method.
+RawConfigParser Objects
+-----------------------
 
+.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True, default_section=configaparser.DEFAULTSECT, interpolation=None)
 
-.. data:: MAX_INTERPOLATION_DEPTH
+   Legacy variant of the :class:`SafeConfigParser` with interpolation disabled
+   by default and an unsafe ``set`` method.
 
-   The maximum depth for recursive interpolation for :meth:`get` when the *raw*
-   parameter is false.  This is relevant only for the :class:`ConfigParser` class.
+   .. note::
+      Consider using :class:`SafeConfigParser` instead which checks types of
+      the values to be stored internally. If you don't want interpolation, you
+      can use ``SafeConfigParser(interpolation=None)``.
 
 
-.. _safeconfigparser-objects:
+   .. method:: set(section, option, value)
 
-SafeConfigParser Objects
-------------------------
+      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:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
+      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.
 
-   Derived class of :class:`ConfigParser` that implements a 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 (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.
 
+.. _configparser-objects:
 
-   The :class:`SafeConfigParser` class implements the same extended interface
-   as :class:`ConfigParser`, with the following addition:
+ConfigParser Objects
+--------------------
 
-   .. method:: set(section, option, value)
+.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True, default_section=configparser.DEFAULTSECT, interpolation=BrokenInterpolation())
 
-      If the given section exists, set the given option to the specified value;
-      otherwise raise :exc:`NoSectionError`.  *value* must be a string; if not,
-      :exc:`TypeError` is raised.
+   .. deprecated:: 3.2
+      Whenever you can, consider using :class:`SafeConfigParser`. The
+      :class:`ConfigParser` provides the same functionality but its
+      implementation is less predictable. It does not validate the
+      interpolation syntax used within a configuration file. It also does not
+      enable escaping the interpolation character (when using
+      :class:`SafeConfigParser`, a key can have ``%`` as part of the value by
+      specifying ``%%`` in the file). On top of that, this class doesn't ensure
+      whether values passed to the parser object are strings which may lead to
+      inconsistent internal state.
 
 
 Exceptions

Modified: python/branches/py3k/Doc/library/fileformats.rst
==============================================================================
--- python/branches/py3k/Doc/library/fileformats.rst	(original)
+++ python/branches/py3k/Doc/library/fileformats.rst	Fri Dec  3 17:28:00 2010
@@ -5,7 +5,7 @@
 ************
 
 The modules described in this chapter parse various miscellaneous file formats
-that aren't markup languages or are related to e-mail.
+that aren't markup languages and are not related to e-mail.
 
 
 .. toctree::

Modified: python/branches/py3k/Lib/configparser.py
==============================================================================
--- python/branches/py3k/Lib/configparser.py	(original)
+++ python/branches/py3k/Lib/configparser.py	Fri Dec  3 17:28:00 2010
@@ -4,23 +4,13 @@
 and followed by "name: value" entries, with continuations and such in
 the style of RFC 822.
 
-The option values can contain format strings which refer to other values in
-the same section, or values in a special [DEFAULT] section.
-
-For example:
-
-    something: %(dir)s/whatever
-
-would resolve the "%(dir)s" to the value of dir.  All reference
-expansions are done late, on demand.
-
 Intrinsic defaults can be specified by passing them into the
-ConfigParser constructor as a dictionary.
+SafeConfigParser constructor as a dictionary.
 
 class:
 
-ConfigParser -- responsible for parsing a list of
-                configuration files, and managing the parsed database.
+SafeConfigParser -- responsible for parsing a list of
+                    configuration files, and managing the parsed database.
 
     methods:
 
@@ -316,7 +306,7 @@
     def filename(self):
         """Deprecated, use `source'."""
         warnings.warn(
-            "This 'filename' attribute will be removed in future versions.  "
+            "The 'filename' attribute will be removed in future versions.  "
             "Use 'source' instead.",
             DeprecationWarning, stacklevel=2
         )
@@ -362,6 +352,204 @@
 _UNSET = object()
 
 
+class Interpolation:
+    """Dummy interpolation that passes the value through with no changes."""
+
+    def before_get(self, parser, section, option, value, defaults):
+        return value
+
+    def before_set(self, parser, section, option, value):
+        return value
+
+    def before_read(self, parser, section, option, value):
+        return value
+
+    def before_write(self, parser, section, option, value):
+        return value
+
+
+class BasicInterpolation(Interpolation):
+    """Interpolation as implemented in the classic SafeConfigParser.
+
+    The option values can contain format strings which refer to other values in
+    the same section, or values in the special default section.
+
+    For example:
+
+        something: %(dir)s/whatever
+
+    would resolve the "%(dir)s" to the value of dir.  All reference
+    expansions are done late, on demand. If a user needs to use a bare % in
+    a configuration file, she can escape it by writing %%. Other other % usage
+    is considered a user error and raises `InterpolationSyntaxError'."""
+
+    _KEYCRE = re.compile(r"%\(([^)]+)\)s")
+
+    def before_get(self, parser, section, option, value, defaults):
+        L = []
+        self._interpolate_some(parser, option, L, value, section, defaults, 1)
+        return ''.join(L)
+
+    def before_set(self, parser, section, option, value):
+        tmp_value = value.replace('%%', '') # escaped percent signs
+        tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
+        if '%' in tmp_value:
+            raise ValueError("invalid interpolation syntax in %r at "
+                             "position %d" % (value, tmp_value.find('%')))
+        return value
+
+    def _interpolate_some(self, parser, option, accum, rest, section, map,
+                          depth):
+        if depth > MAX_INTERPOLATION_DEPTH:
+            raise InterpolationDepthError(option, section, rest)
+        while rest:
+            p = rest.find("%")
+            if p < 0:
+                accum.append(rest)
+                return
+            if p > 0:
+                accum.append(rest[:p])
+                rest = rest[p:]
+            # p is no longer used
+            c = rest[1:2]
+            if c == "%":
+                accum.append("%")
+                rest = rest[2:]
+            elif c == "(":
+                m = self._KEYCRE.match(rest)
+                if m is None:
+                    raise InterpolationSyntaxError(option, section,
+                        "bad interpolation variable reference %r" % rest)
+                var = parser.optionxform(m.group(1))
+                rest = rest[m.end():]
+                try:
+                    v = map[var]
+                except KeyError:
+                    raise InterpolationMissingOptionError(
+                        option, section, rest, var)
+                if "%" in v:
+                    self._interpolate_some(parser, option, accum, v,
+                                           section, map, depth + 1)
+                else:
+                    accum.append(v)
+            else:
+                raise InterpolationSyntaxError(
+                    option, section,
+                    "'%%' must be followed by '%%' or '(', "
+                    "found: %r" % (rest,))
+
+
+class ExtendedInterpolation(Interpolation):
+    """Advanced variant of interpolation, supports the syntax used by
+    `zc.buildout'. Enables interpolation between sections."""
+
+    _KEYCRE = re.compile(r"\$\{([^}]+)\}")
+
+    def before_get(self, parser, section, option, value, defaults):
+        L = []
+        self._interpolate_some(parser, option, L, value, section, defaults, 1)
+        return ''.join(L)
+
+    def before_set(self, parser, section, option, value):
+        tmp_value = value.replace('$$', '') # escaped dollar signs
+        tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
+        if '$' in tmp_value:
+            raise ValueError("invalid interpolation syntax in %r at "
+                             "position %d" % (value, tmp_value.find('%')))
+        return value
+
+    def _interpolate_some(self, parser, option, accum, rest, section, map,
+                          depth):
+        if depth > MAX_INTERPOLATION_DEPTH:
+            raise InterpolationDepthError(option, section, rest)
+        while rest:
+            p = rest.find("$")
+            if p < 0:
+                accum.append(rest)
+                return
+            if p > 0:
+                accum.append(rest[:p])
+                rest = rest[p:]
+            # p is no longer used
+            c = rest[1:2]
+            if c == "$":
+                accum.append("$")
+                rest = rest[2:]
+            elif c == "{":
+                m = self._KEYCRE.match(rest)
+                if m is None:
+                    raise InterpolationSyntaxError(option, section,
+                        "bad interpolation variable reference %r" % rest)
+                path = parser.optionxform(m.group(1)).split(':')
+                rest = rest[m.end():]
+                sect = section
+                opt = option
+                try:
+                    if len(path) == 1:
+                        opt = path[0]
+                        v = map[opt]
+                    elif len(path) == 2:
+                        sect = path[0]
+                        opt = path[1]
+                        v = parser.get(sect, opt, raw=True)
+                    else:
+                        raise InterpolationSyntaxError(
+                            option, section,
+                            "More than one ':' found: %r" % (rest,))
+                except KeyError:
+                    raise InterpolationMissingOptionError(
+                        option, section, rest, var)
+                if "$" in v:
+                    self._interpolate_some(parser, opt, accum, v, sect,
+                                           dict(parser.items(sect, raw=True)),
+                                           depth + 1)
+                else:
+                    accum.append(v)
+            else:
+                raise InterpolationSyntaxError(
+                    option, section,
+                    "'$' must be followed by '$' or '{', "
+                    "found: %r" % (rest,))
+
+
+class BrokenInterpolation(Interpolation):
+    """Deprecated interpolation as implemented in the classic ConfigParser.
+    Use BasicInterpolation or ExtendedInterpolation instead."""
+
+    _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
+
+    def before_get(self, parser, section, option, value, vars):
+        rawval = value
+        depth = MAX_INTERPOLATION_DEPTH
+        while depth:                    # Loop through this until it's done
+            depth -= 1
+            if value and "%(" in value:
+                replace = functools.partial(self._interpolation_replace,
+                                            parser=parser)
+                value = self._KEYCRE.sub(replace, value)
+                try:
+                    value = value % vars
+                except KeyError as e:
+                    raise InterpolationMissingOptionError(
+                        option, section, rawval, e.args[0])
+            else:
+                break
+        if value and "%(" in value:
+            raise InterpolationDepthError(option, section, rawval)
+        return value
+
+    def before_set(self, parser, section, option, value):
+        return value
+
+    @staticmethod
+    def _interpolation_replace(match, parser):
+        s = match.group(1)
+        if s is None:
+            return match.group()
+        else:
+            return "%%(%s)s" % parser.optionxform(s)
+
+
 class RawConfigParser(MutableMapping):
     """ConfigParser that does not do interpolation."""
 
@@ -388,7 +576,8 @@
                                            # space/tab
         (?P<value>.*))?$                   # everything up to eol
         """
-
+    # Interpolation algorithm to be used if the user does not specify another
+    _DEFAULT_INTERPOLATION = Interpolation()
     # Compiled regular expression for matching sections
     SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE)
     # Compiled regular expression for matching options with typical separators
@@ -406,7 +595,15 @@
                  allow_no_value=False, *, delimiters=('=', ':'),
                  comment_prefixes=_COMPATIBLE, strict=False,
                  empty_lines_in_values=True,
-                 default_section=DEFAULTSECT):
+                 default_section=DEFAULTSECT,
+                 interpolation=_UNSET):
+
+        if self.__class__ is RawConfigParser:
+            warnings.warn(
+                "The RawConfigParser class will be removed in future versions."
+                " Use 'SafeConfigParser(interpolation=None)' instead.",
+                DeprecationWarning, stacklevel=2
+            )
         self._dict = dict_type
         self._sections = self._dict()
         self._defaults = self._dict()
@@ -435,7 +632,11 @@
         self._strict = strict
         self._allow_no_value = allow_no_value
         self._empty_lines_in_values = empty_lines_in_values
-        self._default_section=default_section
+        if interpolation is _UNSET:
+            self._interpolation = self._DEFAULT_INTERPOLATION
+        else:
+            self._interpolation = interpolation
+        self.default_section=default_section
 
     def defaults(self):
         return self._defaults
@@ -451,7 +652,7 @@
         Raise DuplicateSectionError if a section by the specified name
         already exists. Raise ValueError if name is DEFAULT.
         """
-        if section == self._default_section:
+        if section == self.default_section:
             raise ValueError('Invalid section name: %s' % section)
 
         if section in self._sections:
@@ -555,7 +756,7 @@
         )
         self.read_file(fp, source=filename)
 
-    def get(self, section, option, *, vars=None, fallback=_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
@@ -563,7 +764,12 @@
         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.
+        If interpolation is enabled and the optional argument `raw' is False,
+        all interpolations are expanded in the return values.
+
+        Arguments `raw', `vars', and `fallback' are keyword only.
+
+        The section DEFAULT is special.
         """
         try:
             d = self._unify_values(section, vars)
@@ -574,61 +780,90 @@
                 return fallback
         option = self.optionxform(option)
         try:
-            return d[option]
+            value = d[option]
         except KeyError:
             if fallback is _UNSET:
                 raise NoOptionError(option, section)
             else:
                 return fallback
 
-    def items(self, section):
-        try:
-            d2 = self._sections[section]
-        except KeyError:
-            if section != self._default_section:
-                raise NoSectionError(section)
-            d2 = self._dict()
-        d = self._defaults.copy()
-        d.update(d2)
-        return d.items()
+        if raw or value is None:
+            return value
+        else:
+            return self._interpolation.before_get(self, section, option, value,
+                                                  d)
 
     def _get(self, section, conv, option, **kwargs):
         return conv(self.get(section, option, **kwargs))
 
-    def getint(self, section, option, *, vars=None, fallback=_UNSET):
+    def getint(self, section, option, *, raw=False, vars=None,
+               fallback=_UNSET):
         try:
-            return self._get(section, int, option, vars=vars)
+            return self._get(section, int, option, raw=raw, vars=vars)
         except (NoSectionError, NoOptionError):
             if fallback is _UNSET:
                 raise
             else:
                 return fallback
 
-    def getfloat(self, section, option, *, vars=None, fallback=_UNSET):
+    def getfloat(self, section, option, *, raw=False, vars=None,
+                 fallback=_UNSET):
         try:
-            return self._get(section, float, option, vars=vars)
+            return self._get(section, float, option, raw=raw, vars=vars)
         except (NoSectionError, NoOptionError):
             if fallback is _UNSET:
                 raise
             else:
                 return fallback
 
-    def getboolean(self, section, option, *, vars=None, fallback=_UNSET):
+    def getboolean(self, section, option, *, raw=False, vars=None,
+                   fallback=_UNSET):
         try:
             return self._get(section, self._convert_to_boolean, option,
-                             vars=vars)
+                             raw=raw, vars=vars)
         except (NoSectionError, NoOptionError):
             if fallback is _UNSET:
                 raise
             else:
                 return fallback
 
+    def items(self, section, raw=False, vars=None):
+        """Return a list of (name, value) tuples for each option in a section.
+
+        All % interpolations are expanded in the return values, based on the
+        defaults passed into the constructor, unless the optional argument
+        `raw' is true.  Additional substitutions may be provided using the
+        `vars' argument, which must be a dictionary whose contents overrides
+        any pre-existing defaults.
+
+        The section DEFAULT is special.
+        """
+        d = self._defaults.copy()
+        try:
+            d.update(self._sections[section])
+        except KeyError:
+            if section != self.default_section:
+                raise NoSectionError(section)
+        # Update with the entry specific variables
+        if vars:
+            for key, value in vars.items():
+                d[self.optionxform(key)] = value
+        options = list(d.keys())
+        if raw:
+            return [(option, d[option])
+                    for option in options]
+        else:
+            return [(option, self._interpolation.before_get(self, section,
+                                                            option, d[option],
+                                                            d))
+                    for option in options]
+
     def optionxform(self, optionstr):
         return optionstr.lower()
 
     def has_option(self, section, option):
         """Check for the existence of a given option in a given section."""
-        if not section or section == self._default_section:
+        if not section or section == self.default_section:
             option = self.optionxform(option)
             return option in self._defaults
         elif section not in self._sections:
@@ -640,7 +875,10 @@
 
     def set(self, section, option, value=None):
         """Set an option."""
-        if not section or section == self._default_section:
+        if value:
+            value = self._interpolation.before_set(self, section, option,
+                                                   value)
+        if not section or section == self.default_section:
             sectdict = self._defaults
         else:
             try:
@@ -660,7 +898,7 @@
         else:
             d = self._delimiters[0]
         if self._defaults:
-            self._write_section(fp, self._default_section,
+            self._write_section(fp, self.default_section,
                                     self._defaults.items(), d)
         for section in self._sections:
             self._write_section(fp, section,
@@ -670,6 +908,8 @@
         """Write a single section to the specified `fp'."""
         fp.write("[{}]\n".format(section_name))
         for key, value in section_items:
+            value = self._interpolation.before_write(self, section_name, key,
+                                                     value)
             if value is not None or not self._allow_no_value:
                 value = delimiter + str(value).replace('\n', '\n\t')
             else:
@@ -679,7 +919,7 @@
 
     def remove_option(self, section, option):
         """Remove an option."""
-        if not section or section == self._default_section:
+        if not section or section == self.default_section:
             sectdict = self._defaults
         else:
             try:
@@ -701,7 +941,7 @@
         return existed
 
     def __getitem__(self, key):
-        if key != self._default_section and not self.has_section(key):
+        if key != self.default_section and not self.has_section(key):
             raise KeyError(key)
         return self._proxies[key]
 
@@ -715,21 +955,21 @@
         self.read_dict({key: value})
 
     def __delitem__(self, key):
-        if key == self._default_section:
+        if key == self.default_section:
             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 == self._default_section or self.has_section(key)
+        return key == self.default_section 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((self._default_section,), self._sections.keys())
+        return itertools.chain((self.default_section,), self._sections.keys())
 
     def _read(self, fp, fpname):
         """Parse a sectioned configuration file.
@@ -801,7 +1041,7 @@
                                                         lineno)
                         cursect = self._sections[sectname]
                         elements_added.add(sectname)
-                    elif sectname == self._default_section:
+                    elif sectname == self.default_section:
                         cursect = self._defaults
                     else:
                         cursect = self._dict()
@@ -836,7 +1076,7 @@
                             cursect[optname] = [optval]
                         else:
                             # valueless option handling
-                            cursect[optname] = optval
+                            cursect[optname] = None
                     else:
                         # a non-fatal parsing error occurred. set up the
                         # exception but keep going. the exception will be
@@ -849,12 +1089,16 @@
         self._join_multiline_values()
 
     def _join_multiline_values(self):
-        all_sections = itertools.chain((self._defaults,),
-                                       self._sections.values())
-        for options in all_sections:
+        defaults = self.default_section, self._defaults
+        all_sections = itertools.chain((defaults,),
+                                       self._sections.items())
+        for section, options in all_sections:
             for name, val in options.items():
                 if isinstance(val, list):
-                    options[name] = '\n'.join(val).rstrip()
+                    val = '\n'.join(val).rstrip()
+                options[name] = self._interpolation.before_read(self,
+                                                                section,
+                                                                name, val)
 
     def _handle_error(self, exc, fpname, lineno, line):
         if not exc:
@@ -871,7 +1115,7 @@
         try:
             d.update(self._sections[section])
         except KeyError:
-            if section != self._default_section:
+            if section != self.default_section:
                 raise NoSectionError(section)
         # Update with the entry specific variables
         if vars:
@@ -906,197 +1150,31 @@
                 raise TypeError("option values must be strings")
 
 
-
 class ConfigParser(RawConfigParser):
     """ConfigParser implementing interpolation."""
 
-    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 `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 fallback is _UNSET:
-                raise
-            else:
-                return fallback
-        option = self.optionxform(option)
-        try:
-            value = d[option]
-        except KeyError:
-            if fallback is _UNSET:
-                raise NoOptionError(option, section)
-            else:
-                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,
-               fallback=_UNSET):
-        try:
-            return self._get(section, int, option, raw=raw, vars=vars)
-        except (NoSectionError, NoOptionError):
-            if fallback is _UNSET:
-                raise
-            else:
-                return fallback
-
-    def getfloat(self, section, option, *, raw=False, vars=None,
-                 fallback=_UNSET):
-        try:
-            return self._get(section, float, option, raw=raw, vars=vars)
-        except (NoSectionError, NoOptionError):
-            if fallback is _UNSET:
-                raise
-            else:
-                return fallback
-
-    def getboolean(self, section, option, *, raw=False, vars=None,
-                   fallback=_UNSET):
-        try:
-            return self._get(section, self._convert_to_boolean, option,
-                             raw=raw, vars=vars)
-        except (NoSectionError, NoOptionError):
-            if fallback is _UNSET:
-                raise
-            else:
-                return fallback
-
-    def items(self, section, raw=False, vars=None):
-        """Return a list of (name, value) tuples for each option in a section.
-
-        All % interpolations are expanded in the return values, based on the
-        defaults passed into the constructor, unless the optional argument
-        `raw' is true.  Additional substitutions may be provided using the
-        `vars' argument, which must be a dictionary whose contents overrides
-        any pre-existing defaults.
-
-        The section DEFAULT is special.
-        """
-        d = self._defaults.copy()
-        try:
-            d.update(self._sections[section])
-        except KeyError:
-            if section != self._default_section:
-                raise NoSectionError(section)
-        # Update with the entry specific variables
-        if vars:
-            for key, value in vars.items():
-                d[self.optionxform(key)] = value
-        options = list(d.keys())
-        if raw:
-            return [(option, d[option])
-                    for option in options]
-        else:
-            return [(option, self._interpolate(section, option, d[option], d))
-                    for option in options]
-
-    def _interpolate(self, section, option, rawval, vars):
-        # do the string interpolation
-        value = rawval
-        depth = MAX_INTERPOLATION_DEPTH
-        while depth:                    # Loop through this until it's done
-            depth -= 1
-            if value and "%(" in value:
-                value = self._KEYCRE.sub(self._interpolation_replace, value)
-                try:
-                    value = value % vars
-                except KeyError as e:
-                    raise InterpolationMissingOptionError(
-                        option, section, rawval, e.args[0])
-            else:
-                break
-        if value and "%(" in value:
-            raise InterpolationDepthError(option, section, rawval)
-        return value
-
-    _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
+    _DEFAULT_INTERPOLATION = BrokenInterpolation()
 
-    def _interpolation_replace(self, match):
-        s = match.group(1)
-        if s is None:
-            return match.group()
-        else:
-            return "%%(%s)s" % self.optionxform(s)
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        if self.__class__ is ConfigParser:
+            warnings.warn(
+                "The ConfigParser class will be removed in future versions."
+                " Use SafeConfigParser instead.",
+                DeprecationWarning, stacklevel=2
+            )
 
 
 class SafeConfigParser(ConfigParser):
     """ConfigParser implementing sane interpolation."""
 
-    def _interpolate(self, section, option, rawval, vars):
-        # do the string interpolation
-        L = []
-        self._interpolate_some(option, L, rawval, section, vars, 1)
-        return ''.join(L)
-
-    _interpvar_re = re.compile(r"%\(([^)]+)\)s")
-
-    def _interpolate_some(self, option, accum, rest, section, map, depth):
-        if depth > MAX_INTERPOLATION_DEPTH:
-            raise InterpolationDepthError(option, section, rest)
-        while rest:
-            p = rest.find("%")
-            if p < 0:
-                accum.append(rest)
-                return
-            if p > 0:
-                accum.append(rest[:p])
-                rest = rest[p:]
-            # p is no longer used
-            c = rest[1:2]
-            if c == "%":
-                accum.append("%")
-                rest = rest[2:]
-            elif c == "(":
-                m = self._interpvar_re.match(rest)
-                if m is None:
-                    raise InterpolationSyntaxError(option, section,
-                        "bad interpolation variable reference %r" % rest)
-                var = self.optionxform(m.group(1))
-                rest = rest[m.end():]
-                try:
-                    v = map[var]
-                except KeyError:
-                    raise InterpolationMissingOptionError(
-                        option, section, rest, var)
-                if "%" in v:
-                    self._interpolate_some(option, accum, v,
-                                           section, map, depth + 1)
-                else:
-                    accum.append(v)
-            else:
-                raise InterpolationSyntaxError(
-                    option, section,
-                    "'%%' must be followed by '%%' or '(', "
-                    "found: %r" % (rest,))
+    _DEFAULT_INTERPOLATION = BasicInterpolation()
 
     def set(self, section, option, value=None):
-        """Set an option.  Extend ConfigParser.set: check for string values."""
+        """Set an option.  Extends RawConfigParser.set by validating type and
+        interpolation syntax on the value."""
         self._validate_value_type(value)
-        # check for bad percent signs
-        if value:
-            tmp_value = value.replace('%%', '') # escaped percent signs
-            tmp_value = self._interpvar_re.sub('', tmp_value) # valid syntax
-            if '%' in tmp_value:
-                raise ValueError("invalid interpolation syntax in %r at "
-                                "position %d" % (value, tmp_value.find('%')))
-        ConfigParser.set(self, section, option, value)
+        super().set(section, option, value)
 
 
 class SectionProxy(MutableMapping):

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	Fri Dec  3 17:28:00 2010
@@ -4,6 +4,7 @@
 import os
 import unittest
 import textwrap
+import warnings
 
 from test import support
 
@@ -32,6 +33,7 @@
     dict_type = configparser._default_dict
     strict = False
     default_section = configparser.DEFAULTSECT
+    interpolation = configparser._UNSET
 
     def newconfig(self, defaults=None):
         arguments = dict(
@@ -43,8 +45,12 @@
             dict_type=self.dict_type,
             strict=self.strict,
             default_section=self.default_section,
+            interpolation=self.interpolation,
         )
-        return self.config_class(**arguments)
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", category=DeprecationWarning)
+            instance = self.config_class(**arguments)
+        return instance
 
     def fromstring(self, string, defaults=None):
         cf = self.newconfig(defaults)
@@ -847,6 +853,70 @@
         cf = self.newconfig()
         self.assertRaises(ValueError, cf.add_section, self.default_section)
 
+class SafeConfigParserTestCaseExtendedInterpolation(BasicTestCase):
+    config_class = configparser.SafeConfigParser
+    interpolation = configparser.ExtendedInterpolation()
+    default_section = 'common'
+
+    def test_extended_interpolation(self):
+        cf = self.fromstring(textwrap.dedent("""
+            [common]
+            favourite Beatle = Paul
+            favourite color = green
+
+            [tom]
+            favourite band = ${favourite color} day
+            favourite pope = John ${favourite Beatle} II
+            sequel = ${favourite pope}I
+
+            [ambv]
+            favourite Beatle = George
+            son of Edward VII = ${favourite Beatle} V
+            son of George V = ${son of Edward VII}I
+
+            [stanley]
+            favourite Beatle = ${ambv:favourite Beatle}
+            favourite pope = ${tom:favourite pope}
+            favourite color = black
+            favourite state of mind = paranoid
+            favourite movie = soylent ${common:favourite color}
+            favourite song = ${favourite color} sabbath - ${favourite state of mind}
+        """).strip())
+
+        eq = self.assertEqual
+        eq(cf['common']['favourite Beatle'], 'Paul')
+        eq(cf['common']['favourite color'], 'green')
+        eq(cf['tom']['favourite Beatle'], 'Paul')
+        eq(cf['tom']['favourite color'], 'green')
+        eq(cf['tom']['favourite band'], 'green day')
+        eq(cf['tom']['favourite pope'], 'John Paul II')
+        eq(cf['tom']['sequel'], 'John Paul III')
+        eq(cf['ambv']['favourite Beatle'], 'George')
+        eq(cf['ambv']['favourite color'], 'green')
+        eq(cf['ambv']['son of Edward VII'], 'George V')
+        eq(cf['ambv']['son of George V'], 'George VI')
+        eq(cf['stanley']['favourite Beatle'], 'George')
+        eq(cf['stanley']['favourite color'], 'black')
+        eq(cf['stanley']['favourite state of mind'], 'paranoid')
+        eq(cf['stanley']['favourite movie'], 'soylent green')
+        eq(cf['stanley']['favourite pope'], 'John Paul II')
+        eq(cf['stanley']['favourite song'],
+           'black sabbath - paranoid')
+
+    def test_endless_loop(self):
+        cf = self.fromstring(textwrap.dedent("""
+            [one for you]
+            ping = ${one for me:pong}
+
+            [one for me]
+            pong = ${one for you:ping}
+        """).strip())
+
+        with self.assertRaises(configparser.InterpolationDepthError):
+            cf['one for you']['ping']
+
+
+
 class SafeConfigParserTestCaseNonStandardDelimiters(SafeConfigParserTestCase):
     delimiters = (':=', '$')
     comment_prefixes = ('//', '"')
@@ -910,7 +980,9 @@
 
     def prepare(self, config_class):
         # This is the default, but that's the point.
-        cp = config_class(allow_no_value=False)
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", category=DeprecationWarning)
+            cp = config_class(allow_no_value=False)
         cp.add_section("section")
         cp.set("section", "option", None)
         sio = io.StringIO()
@@ -978,6 +1050,7 @@
         RawConfigParserTestCaseNonStandardDelimiters,
         RawConfigParserTestSambaConf,
         SafeConfigParserTestCase,
+        SafeConfigParserTestCaseExtendedInterpolation,
         SafeConfigParserTestCaseNonStandardDelimiters,
         SafeConfigParserTestCaseNoValue,
         SafeConfigParserTestCaseTrickyFile,

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Fri Dec  3 17:28:00 2010
@@ -149,6 +149,14 @@
 - Issue #10467: Fix BytesIO.readinto() after seeking into a position after the
   end of the file.
 
+- configparser: the ConfigParser class has been deprecated in favor of
+  SafeConfigParser. Usage of RawConfigParser is now discouraged for new
+  projects in favor of SafeConfigParser(interpolation=None).
+
+- Issue #10499: configparser supports pluggable interpolation handlers. New
+  interpolation handler added (ExtendedInterpolation) which supports the syntax
+  used by zc.buildout (e.g. interpolation between sections).
+
 - Issue #1682942: configparser supports alternative option/value delimiters.
 
 - Issue #5412: configparser supports mapping protocol access.


More information about the Python-checkins mailing list