Kaydet (Commit) b6a6f5f8 authored tarafından Łukasz Langa's avatar Łukasz Langa

Issue 10499: Modular interpolation in configparser

üst ecace28e
......@@ -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
......
......@@ -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::
......
......@@ -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):
......
......@@ -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,
......
......@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment