summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorŁukasz Langa <lukasz@langa.pl>2010-12-03 16:28:00 (GMT)
committerŁukasz Langa <lukasz@langa.pl>2010-12-03 16:28:00 (GMT)
commitb6a6f5f886ed869612e16cd1e29a1190996dc78d (patch)
tree43e86bc6227ab409723a87c9d6ae8448c976f9da
parentecace28ef4486b3fee598e3854431d3a454e30f8 (diff)
downloadcpython-b6a6f5f886ed869612e16cd1e29a1190996dc78d.zip
cpython-b6a6f5f886ed869612e16cd1e29a1190996dc78d.tar.gz
cpython-b6a6f5f886ed869612e16cd1e29a1190996dc78d.tar.bz2
Issue 10499: Modular interpolation in configparser
-rw-r--r--Doc/library/configparser.rst483
-rw-r--r--Doc/library/fileformats.rst2
-rw-r--r--Lib/configparser.py540
-rw-r--r--Lib/test/test_cfgparser.py77
-rw-r--r--Misc/NEWS8
5 files changed, 652 insertions, 458 deletions
diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst
index df7a1de1..6822176 100644
--- a/Doc/library/configparser.rst
+++ b/Doc/library/configparser.rst
@@ -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 @@ customized by end users easily.
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 @@ Let's take a very basic configuration file that looks like this:
.. 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 @@ creating the above configuration file programatically.
.. doctest::
>>> import configparser
- >>> config = configparser.RawConfigParser()
+ >>> config = configparser.SafeConfigParser()
>>> config['DEFAULT'] = {'ServerAliveInterval': '45',
... 'Compression': 'yes',
... 'CompressionLevel': '9'}
@@ -89,7 +92,7 @@ back and explore the data it holds.
.. doctest::
>>> import configparser
- >>> config = configparser.RawConfigParser()
+ >>> config = configparser.SafeConfigParser()
>>> config.sections()
[]
>>> config.read('example.ini')
@@ -233,23 +236,26 @@ by a whitespace character to be recognized as a comment. For backwards
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 @@ For example:
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 @@ 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.
+ 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 @@ the :meth:`__init__` options:
.. doctest::
- >>> parser = configparser.RawConfigParser()
+ >>> parser = configparser.SafeConfigParser()
>>> parser.read_dict({'section1': {'key1': 'value1',
... 'key2': 'value2',
... 'key3': 'value3'},
@@ -395,7 +465,7 @@ the :meth:`__init__` options:
.. doctest::
>>> from collections import OrderedDict
- >>> parser = configparser.RawConfigParser()
+ >>> parser = configparser.SafeConfigParser()
>>> parser.read_dict(
... OrderedDict((
... ('s1',
@@ -441,7 +511,7 @@ the :meth:`__init__` options:
... 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 @@ the :meth:`__init__` options:
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 @@ the :meth:`__init__` options:
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 @@ may be overriden by subclasses or by attribute assignment.
.. doctest::
- >>> custom = configparser.RawConfigParser()
+ >>> custom = configparser.SafeConfigParser()
>>> custom['section1'] = {'funky': 'nope'}
>>> custom['section1'].getboolean('funky')
Traceback (most recent call last):
@@ -557,7 +652,7 @@ may be overriden by subclasses or by attribute assignment.
... [Section2]
... AnotherKey = Value
... """
- >>> typical = configparser.RawConfigParser()
+ >>> typical = configparser.SafeConfigParser()
>>> typical.read_string(config)
>>> list(typical['Section1'].keys())
['key']
@@ -623,8 +718,7 @@ An example of reading the configuration file again::
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 @@ used in interpolation if an option used is not defined elsewhere. ::
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 @@ RawConfigParser Objects
(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 @@ RawConfigParser Objects
.. 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 @@ RawConfigParser Objects
.. 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 @@ RawConfigParser Objects
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 @@ RawConfigParser Objects
.. 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 @@ RawConfigParser Objects
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 @@ RawConfigParser Objects
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])
-
- 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*.
-
-
- .. method:: items(section, raw=False, vars=None)
-
- 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.
-
-
.. 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.
+ parameter is false. This is relevant only when the default *interpolation*
+ is used.
-.. _safeconfigparser-objects:
+.. _rawconfigparser-objects:
-SafeConfigParser Objects
-------------------------
+RawConfigParser Objects
+-----------------------
-.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
+.. 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)
- 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).
+ Legacy variant of the :class:`SafeConfigParser` with interpolation disabled
+ by default and an unsafe ``set`` method.
- Applications that don't require interpolation should use
- :class:`RawConfigParser`, otherwise :class:`SafeConfigParser` is the best
- option.
+ .. 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)``.
- .. 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:: 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.
+ 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.
- The :class:`SafeConfigParser` class implements the same extended interface
- as :class:`ConfigParser`, with the following addition:
- .. method:: set(section, option, value)
+.. _configparser-objects:
- 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.
+ConfigParser Objects
+--------------------
+
+.. 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())
+
+ .. 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
diff --git a/Doc/library/fileformats.rst b/Doc/library/fileformats.rst
index 980d4f5..e9c2e1f 100644
--- a/Doc/library/fileformats.rst
+++ b/Doc/library/fileformats.rst
@@ -5,7 +5,7 @@ File Formats
************
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::
diff --git a/Lib/configparser.py b/Lib/configparser.py
index f9bb32c..56c02ba 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -4,23 +4,13 @@ A configuration file consists of sections, lead by a "[section]" header,
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 @@ class ParsingError(Error):
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 @@ _COMPATIBLE = object()
_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 @@ class RawConfigParser(MutableMapping):
# 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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
)
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
"""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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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 @@ class RawConfigParser(MutableMapping):
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)
+ _DEFAULT_INTERPOLATION = BrokenInterpolation()
- 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|.")
-
- 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):
diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py
index 38dd34b..1ae720e 100644
--- a/Lib/test/test_cfgparser.py
+++ b/Lib/test/test_cfgparser.py
@@ -4,6 +4,7 @@ import io
import os
import unittest
import textwrap
+import warnings
from test import support
@@ -32,6 +33,7 @@ class CfgParserTestCaseClass(unittest.TestCase):
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 @@ class CfgParserTestCaseClass(unittest.TestCase):
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 @@ class SafeConfigParserTestCase(ConfigParserTestCase):
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 @@ class Issue7005TestCase(unittest.TestCase):
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 @@ def test_main():
RawConfigParserTestCaseNonStandardDelimiters,
RawConfigParserTestSambaConf,
SafeConfigParserTestCase,
+ SafeConfigParserTestCaseExtendedInterpolation,
SafeConfigParserTestCaseNonStandardDelimiters,
SafeConfigParserTestCaseNoValue,
SafeConfigParserTestCaseTrickyFile,
diff --git a/Misc/NEWS b/Misc/NEWS
index 13c29cd..0349ef2 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -149,6 +149,14 @@ Library
- 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.