Issue 10499: Modular interpolation in configparser

This commit is contained in:
Łukasz Langa 2010-12-03 16:28:00 +00:00
parent ecace28ef4
commit b6a6f5f886
5 changed files with 691 additions and 497 deletions

View File

@ -17,11 +17,10 @@
single: ini file single: ini file
single: Windows ini file single: Windows ini file
This module provides the classes :class:`RawConfigParser` and This module provides the :class:`SafeConfigParser` class which implements
:class:`SafeConfigParser`. They implement a basic configuration a basic configuration language which provides a structure similar to what's
language which provides a structure similar to what's found in Microsoft found in Microsoft Windows INI files. You can use this to write Python
Windows INI files. You can use this to write Python programs which can be programs which can be customized by end users easily.
customized by end users easily.
.. note:: .. note::
@ -34,6 +33,10 @@ customized by end users easily.
Support for a creating Unix shell-like mini-languages which can be used Support for a creating Unix shell-like mini-languages which can be used
as an alternate format for application configuration files. 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 Quick Start
----------- -----------
@ -64,7 +67,7 @@ creating the above configuration file programatically.
.. doctest:: .. doctest::
>>> import configparser >>> import configparser
>>> config = configparser.RawConfigParser() >>> config = configparser.SafeConfigParser()
>>> config['DEFAULT'] = {'ServerAliveInterval': '45', >>> config['DEFAULT'] = {'ServerAliveInterval': '45',
... 'Compression': 'yes', ... 'Compression': 'yes',
... 'CompressionLevel': '9'} ... 'CompressionLevel': '9'}
@ -89,7 +92,7 @@ back and explore the data it holds.
.. doctest:: .. doctest::
>>> import configparser >>> import configparser
>>> config = configparser.RawConfigParser() >>> config = configparser.SafeConfigParser()
>>> config.sections() >>> config.sections()
[] []
>>> config.read('example.ini') >>> config.read('example.ini')
@ -233,19 +236,22 @@ by a whitespace character to be recognized as a comment. For backwards
compatibility, by default only ``;`` starts an inline comment, while compatibility, by default only ``;`` starts an inline comment, while
``#`` does not [1]_. ``#`` 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: For example:
.. code-block:: ini .. code-block:: ini
[Paths] [Simple Values]
home_dir: /Users key: value
my_dir: %(home_dir)s/lumberjack spaces in keys: allowed
my_pictures: %(my_dir)s/Pictures 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] [Multiline Values]
chorus: I'm a lumberjack, and I'm okay chorus: I'm a lumberjack, and I'm okay
@ -273,17 +279,81 @@ For example:
of a value of a value
# Did I mention we can indent comments, too? # 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 Interpolation of values
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.
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 Mapping Protocol Access
----------------------- -----------------------
@ -350,9 +420,9 @@ the :meth:`__init__` options:
* *defaults*, default value: ``None`` * *defaults*, default value: ``None``
This option accepts a dictionary of key-value pairs which will be initially 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 put in the ``DEFAULT`` section. This makes for an elegant way to support
configuration files that don't specify values which are the same as the concise configuration files that don't specify values which are the same as
documented default. the documented default.
Hint: if you want to specify default values for a specific section, use Hint: if you want to specify default values for a specific section, use
:meth:`read_dict` before you read the actual file. :meth:`read_dict` before you read the actual file.
@ -374,7 +444,7 @@ the :meth:`__init__` options:
.. doctest:: .. doctest::
>>> parser = configparser.RawConfigParser() >>> parser = configparser.SafeConfigParser()
>>> parser.read_dict({'section1': {'key1': 'value1', >>> parser.read_dict({'section1': {'key1': 'value1',
... 'key2': 'value2', ... 'key2': 'value2',
... 'key3': 'value3'}, ... 'key3': 'value3'},
@ -395,7 +465,7 @@ the :meth:`__init__` options:
.. doctest:: .. doctest::
>>> from collections import OrderedDict >>> from collections import OrderedDict
>>> parser = configparser.RawConfigParser() >>> parser = configparser.SafeConfigParser()
>>> parser.read_dict( >>> parser.read_dict(
... OrderedDict(( ... OrderedDict((
... ('s1', ... ('s1',
@ -441,7 +511,7 @@ the :meth:`__init__` options:
... skip-bdb ... skip-bdb
... skip-innodb # we don't need ACID today ... 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) >>> config.read_string(sample_config)
>>> # Settings with values are treated as before: >>> # 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. This means values (but not keys) can contain the delimiters.
See also the *space_around_delimiters* argument to See also the *space_around_delimiters* argument to
:meth:`RawConfigParser.write`. :meth:`SafeConfigParser.write`.
* *comment_prefixes*, default value: ``_COMPATIBLE`` (``'#'`` valid on empty * *comment_prefixes*, default value: ``_COMPATIBLE`` (``'#'`` valid on empty
lines, ``';'`` valid also on non-empty lines) 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 will make empty lines split keys every time. In the example above, it would
produce two keys, ``key`` and ``this``. 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 More advanced customization may be achieved by overriding default values of
these parser attributes. The defaults are defined on the classes, so they 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:: .. doctest::
>>> custom = configparser.RawConfigParser() >>> custom = configparser.SafeConfigParser()
>>> custom['section1'] = {'funky': 'nope'} >>> custom['section1'] = {'funky': 'nope'}
>>> custom['section1'].getboolean('funky') >>> custom['section1'].getboolean('funky')
Traceback (most recent call last): Traceback (most recent call last):
@ -557,7 +652,7 @@ may be overriden by subclasses or by attribute assignment.
... [Section2] ... [Section2]
... AnotherKey = Value ... AnotherKey = Value
... """ ... """
>>> typical = configparser.RawConfigParser() >>> typical = configparser.SafeConfigParser()
>>> typical.read_string(config) >>> typical.read_string(config)
>>> list(typical['Section1'].keys()) >>> list(typical['Section1'].keys())
['key'] ['key']
@ -623,8 +718,7 @@ An example of reading the configuration file again::
if config.getboolean('Section1', 'bool'): if config.getboolean('Section1', 'bool'):
print(config.get('Section1', 'foo')) print(config.get('Section1', 'foo'))
To get interpolation, use :class:`SafeConfigParser` or, if To get interpolation, use :class:`SafeConfigParser`::
you absolutely have to, a :class:`ConfigParser`::
import configparser 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!" 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 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 will be used to create the dictionary objects for the list of sections, for
the options within a section, and for the default values. 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, (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. internal empty lines of a multiline option are kept as part of the value.
When *allow_no_value* is ``True`` (default: ``False``), options without 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.
This class does not support the magical interpolation behavior. 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>`_.
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 .. versionchanged:: 3.1
The default *dict_type* is :class:`collections.OrderedDict`. The default *dict_type* is :class:`collections.OrderedDict`.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
*allow_no_value*, *delimiters*, *comment_prefixes*, *strict* and *allow_no_value*, *delimiters*, *comment_prefixes*, *strict*,
*empty_lines_in_values* were added. *empty_lines_in_values*, *default_section* and *interpolation* were
added.
.. method:: defaults() .. method:: defaults()
@ -717,22 +828,21 @@ RawConfigParser Objects
.. method:: sections() .. method:: sections()
Return a list of the sections available; ``DEFAULT`` is not included in Return a list of the sections available; the *default section* is not
the list. included in the list.
.. method:: add_section(section) .. method:: add_section(section)
Add a section named *section* to the instance. If a section by the given Add a section named *section* to the instance. If a section by the given
name already exists, :exc:`DuplicateSectionError` is raised. If the name name already exists, :exc:`DuplicateSectionError` is raised. If the
``DEFAULT`` (or any of it's case-insensitive variants) is passed, *default section* name is passed, :exc:`ValueError` is raised.
:exc:`ValueError` is raised.
.. method:: has_section(section) .. method:: has_section(section)
Indicates whether the named section is present in the configuration. The Indicates whether the named *section* is present in the configuration.
``DEFAULT`` section is not acknowledged. The *default section* is not acknowledged.
.. method:: options(section) .. method:: options(section)
@ -742,7 +852,7 @@ RawConfigParser Objects
.. method:: has_option(section, option) .. 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`. :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 Attempt to read and parse a list of filenames, returning a list of
filenames which were successfully parsed. If *filenames* is a string, it 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 is treated as a single filename. If a file named in *filenames* cannot
opened, that file will be ignored. This is designed so that you can 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 specify a list of potential configuration file locations (for example,
current directory, the user's home directory, and some system-wide the current directory, the user's home directory, and some system-wide
directory), and all existing configuration files in the list will be read. directory), and all existing configuration files in the list will be
If none of the named files exist, the :class:`ConfigParser` instance will read. If none of the named files exist, the :class:`ConfigParser`
contain an empty dataset. An application which requires initial values to instance will contain an empty dataset. An application which requires
be loaded from a file should load the required file or files using initial values to be loaded from a file should load the required file or
:meth:`read_file` before calling :meth:`read` for any optional files:: files using :meth:`read_file` before calling :meth:`read` for any
optional files::
import configparser, os import configparser, os
config = configparser.ConfigParser() config = configparser.SafeConfigParser()
config.read_file(open('defaults.cfg')) config.read_file(open('defaults.cfg'))
config.read(['site.cfg', os.path.expanduser('~/.myapp.cfg')], config.read(['site.cfg', os.path.expanduser('~/.myapp.cfg')],
encoding='cp1250') encoding='cp1250')
@ -810,154 +921,6 @@ RawConfigParser Objects
.. versionadded:: 3.2 .. versionadded:: 3.2
.. method:: get(section, option, [vars, fallback])
Get an *option* value for the named *section*. If *vars* is provided, it
must be a dictionary. The *option* is looked up in *vars* (if provided),
*section*, and in *DEFAULTSECT* in that order. If the key is not found
and *fallback* is provided, it is used as a fallback value. ``None`` can
be provided as a *fallback* value.
.. versionchanged:: 3.2
Arguments *vars* and *fallback* are keyword only to protect users from
trying to use the third argument as the *fallback* fallback (especially
when using the mapping protocol).
.. method:: getint(section, option, [vars, fallback])
A convenience method which coerces the *option* in the specified *section*
to an integer. See :meth:`get` for explanation of *vars* and *fallback*.
.. method:: getfloat(section, option, [vars, fallback])
A convenience method which coerces the *option* in the specified *section*
to a floating point number. See :meth:`get` for explanation of *vars* and
*fallback*.
.. method:: getboolean(section, option, [vars, fallback])
A convenience method which coerces the *option* in the specified *section*
to a Boolean value. Note that the accepted values for the option are
``"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
*fallback*.
.. method:: items(section)
Return a list of *name*, *value* pairs for each option in the given
*section*.
.. 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.
.. method:: write(fileobject, space_around_delimiters=True)
Write a representation of the configuration to the specified :term:`file
object`, which must be opened in text mode (accepting strings). This
representation can be parsed by a future :meth:`read` call. If
*space_around_delimiters* is true, delimiters between
keys and values are surrounded by spaces.
.. method:: remove_option(section, option)
Remove the specified *option* from the specified *section*. If the
section does not exist, raise :exc:`NoSectionError`. If the option
existed to be removed, return :const:`True`; otherwise return
:const:`False`.
.. method:: remove_section(section)
Remove the specified *section* from the configuration. If the section in
fact existed, return ``True``. Otherwise return ``False``.
.. method:: optionxform(option)
Transforms the option name *option* as found in an input file or as passed
in by client code to the form that should be used in the internal
structures. The default implementation returns a lower-case version of
*option*; subclasses may override this or client code can set an attribute
of this name on instances to affect this behavior.
You don't need to subclass the parser to use this method, you can also
set it on an instance, to a function that takes a string argument and
returns a string. Setting it to ``str``, for example, would make option
names case sensitive::
cfgparser = ConfigParser()
cfgparser.optionxform = str
Note that when reading configuration files, whitespace around the option
names is stripped before :meth:`optionxform` is called.
.. method:: readfp(fp, filename=None)
.. deprecated:: 3.2
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]) .. method:: get(section, option, raw=False, [vars, fallback])
@ -1010,40 +973,6 @@ The :class:`ConfigParser` class extends some methods of the
:meth:`get` method. :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.
.. _safeconfigparser-objects:
SafeConfigParser Objects
------------------------
.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
Derived class of :class:`ConfigParser` that implements a variant of the
magical interpolation feature. This implementation is more predictable as
it validates the interpolation syntax used within a configuration file.
This class also enables escaping the interpolation character (a key can have
``%`` as part of the value by specifying ``%%`` in the file).
Applications that don't require interpolation should use
:class:`RawConfigParser`, otherwise :class:`SafeConfigParser` is the best
option.
.. versionchanged:: 3.1
The default *dict_type* is :class:`collections.OrderedDict`.
.. versionchanged:: 3.2
*allow_no_value*, *delimiters*, *comment_prefixes*, *strict* and
*empty_lines_in_values* were added.
The :class:`SafeConfigParser` class implements the same extended interface
as :class:`ConfigParser`, with the following addition:
.. method:: set(section, option, value) .. method:: set(section, option, value)
If the given section exists, set the given option to the specified value; If the given section exists, set the given option to the specified value;
@ -1051,6 +980,112 @@ SafeConfigParser Objects
:exc:`TypeError` is raised. :exc:`TypeError` is raised.
.. method:: write(fileobject, space_around_delimiters=True)
Write a representation of the configuration to the specified :term:`file
object`, which must be opened in text mode (accepting strings). This
representation can be parsed by a future :meth:`read` call. If
*space_around_delimiters* is true, delimiters between
keys and values are surrounded by spaces.
.. method:: remove_option(section, option)
Remove the specified *option* from the specified *section*. If the
section does not exist, raise :exc:`NoSectionError`. If the option
existed to be removed, return :const:`True`; otherwise return
:const:`False`.
.. method:: remove_section(section)
Remove the specified *section* from the configuration. If the section in
fact existed, return ``True``. Otherwise return ``False``.
.. method:: optionxform(option)
Transforms the option name *option* as found in an input file or as passed
in by client code to the form that should be used in the internal
structures. The default implementation returns a lower-case version of
*option*; subclasses may override this or client code can set an attribute
of this name on instances to affect this behavior.
You don't need to subclass the parser to use this method, you can also
set it on an instance, to a function that takes a string argument and
returns a string. Setting it to ``str``, for example, would make option
names case sensitive::
cfgparser = ConfigParser()
cfgparser.optionxform = str
Note that when reading configuration files, whitespace around the option
names is stripped before :meth:`optionxform` is called.
.. method:: readfp(fp, filename=None)
.. deprecated:: 3.2
Use :meth:`read_file` instead.
.. data:: MAX_INTERPOLATION_DEPTH
The maximum depth for recursive interpolation for :meth:`get` when the *raw*
parameter is false. This is relevant only when the default *interpolation*
is used.
.. _rawconfigparser-objects:
RawConfigParser Objects
-----------------------
.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True, default_section=configaparser.DEFAULTSECT, interpolation=None)
Legacy variant of the :class:`SafeConfigParser` with interpolation disabled
by default and an unsafe ``set`` method.
.. 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)``.
.. 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.
.. _configparser-objects:
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 Exceptions
---------- ----------

View File

@ -5,7 +5,7 @@ File Formats
************ ************
The modules described in this chapter parse various miscellaneous 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:: .. toctree::

View File

@ -4,22 +4,12 @@ A configuration file consists of sections, lead by a "[section]" header,
and followed by "name: value" entries, with continuations and such in and followed by "name: value" entries, with continuations and such in
the style of RFC 822. 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 Intrinsic defaults can be specified by passing them into the
ConfigParser constructor as a dictionary. SafeConfigParser constructor as a dictionary.
class: class:
ConfigParser -- responsible for parsing a list of SafeConfigParser -- responsible for parsing a list of
configuration files, and managing the parsed database. configuration files, and managing the parsed database.
methods: methods:
@ -316,7 +306,7 @@ class ParsingError(Error):
def filename(self): def filename(self):
"""Deprecated, use `source'.""" """Deprecated, use `source'."""
warnings.warn( warnings.warn(
"This 'filename' attribute will be removed in future versions. " "The 'filename' attribute will be removed in future versions. "
"Use 'source' instead.", "Use 'source' instead.",
DeprecationWarning, stacklevel=2 DeprecationWarning, stacklevel=2
) )
@ -362,6 +352,204 @@ _COMPATIBLE = object()
_UNSET = 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): class RawConfigParser(MutableMapping):
"""ConfigParser that does not do interpolation.""" """ConfigParser that does not do interpolation."""
@ -388,7 +576,8 @@ class RawConfigParser(MutableMapping):
# space/tab # space/tab
(?P<value>.*))?$ # everything up to eol (?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 # Compiled regular expression for matching sections
SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE) SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE)
# Compiled regular expression for matching options with typical separators # Compiled regular expression for matching options with typical separators
@ -406,7 +595,15 @@ class RawConfigParser(MutableMapping):
allow_no_value=False, *, delimiters=('=', ':'), allow_no_value=False, *, delimiters=('=', ':'),
comment_prefixes=_COMPATIBLE, strict=False, comment_prefixes=_COMPATIBLE, strict=False,
empty_lines_in_values=True, 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._dict = dict_type
self._sections = self._dict() self._sections = self._dict()
self._defaults = self._dict() self._defaults = self._dict()
@ -435,7 +632,11 @@ class RawConfigParser(MutableMapping):
self._strict = strict self._strict = strict
self._allow_no_value = allow_no_value self._allow_no_value = allow_no_value
self._empty_lines_in_values = empty_lines_in_values 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): def defaults(self):
return self._defaults return self._defaults
@ -451,7 +652,7 @@ class RawConfigParser(MutableMapping):
Raise DuplicateSectionError if a section by the specified name Raise DuplicateSectionError if a section by the specified name
already exists. Raise ValueError if name is DEFAULT. 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) raise ValueError('Invalid section name: %s' % section)
if section in self._sections: if section in self._sections:
@ -555,7 +756,7 @@ class RawConfigParser(MutableMapping):
) )
self.read_file(fp, source=filename) 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. """Get an option value for a given section.
If `vars' is provided, it must be a dictionary. The option is looked up 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 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. 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: try:
d = self._unify_values(section, vars) d = self._unify_values(section, vars)
@ -574,61 +780,90 @@ class RawConfigParser(MutableMapping):
return fallback return fallback
option = self.optionxform(option) option = self.optionxform(option)
try: try:
return d[option] value = d[option]
except KeyError: except KeyError:
if fallback is _UNSET: if fallback is _UNSET:
raise NoOptionError(option, section) raise NoOptionError(option, section)
else: else:
return fallback return fallback
def items(self, section): if raw or value is None:
try: return value
d2 = self._sections[section] else:
except KeyError: return self._interpolation.before_get(self, section, option, value,
if section != self._default_section: d)
raise NoSectionError(section)
d2 = self._dict()
d = self._defaults.copy()
d.update(d2)
return d.items()
def _get(self, section, conv, option, **kwargs): def _get(self, section, conv, option, **kwargs):
return conv(self.get(section, 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: try:
return self._get(section, int, option, vars=vars) return self._get(section, int, option, raw=raw, vars=vars)
except (NoSectionError, NoOptionError): except (NoSectionError, NoOptionError):
if fallback is _UNSET: if fallback is _UNSET:
raise raise
else: else:
return fallback return fallback
def getfloat(self, section, option, *, vars=None, fallback=_UNSET): def getfloat(self, section, option, *, raw=False, vars=None,
fallback=_UNSET):
try: try:
return self._get(section, float, option, vars=vars) return self._get(section, float, option, raw=raw, vars=vars)
except (NoSectionError, NoOptionError): except (NoSectionError, NoOptionError):
if fallback is _UNSET: if fallback is _UNSET:
raise raise
else: else:
return fallback return fallback
def getboolean(self, section, option, *, vars=None, fallback=_UNSET): def getboolean(self, section, option, *, raw=False, vars=None,
fallback=_UNSET):
try: try:
return self._get(section, self._convert_to_boolean, option, return self._get(section, self._convert_to_boolean, option,
vars=vars) raw=raw, vars=vars)
except (NoSectionError, NoOptionError): except (NoSectionError, NoOptionError):
if fallback is _UNSET: if fallback is _UNSET:
raise raise
else: else:
return fallback 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): def optionxform(self, optionstr):
return optionstr.lower() return optionstr.lower()
def has_option(self, section, option): def has_option(self, section, option):
"""Check for the existence of a given option in a given section.""" """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) option = self.optionxform(option)
return option in self._defaults return option in self._defaults
elif section not in self._sections: elif section not in self._sections:
@ -640,7 +875,10 @@ class RawConfigParser(MutableMapping):
def set(self, section, option, value=None): def set(self, section, option, value=None):
"""Set an option.""" """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 sectdict = self._defaults
else: else:
try: try:
@ -660,7 +898,7 @@ class RawConfigParser(MutableMapping):
else: else:
d = self._delimiters[0] d = self._delimiters[0]
if self._defaults: if self._defaults:
self._write_section(fp, self._default_section, self._write_section(fp, self.default_section,
self._defaults.items(), d) self._defaults.items(), d)
for section in self._sections: for section in self._sections:
self._write_section(fp, section, self._write_section(fp, section,
@ -670,6 +908,8 @@ class RawConfigParser(MutableMapping):
"""Write a single section to the specified `fp'.""" """Write a single section to the specified `fp'."""
fp.write("[{}]\n".format(section_name)) fp.write("[{}]\n".format(section_name))
for key, value in section_items: 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: if value is not None or not self._allow_no_value:
value = delimiter + str(value).replace('\n', '\n\t') value = delimiter + str(value).replace('\n', '\n\t')
else: else:
@ -679,7 +919,7 @@ class RawConfigParser(MutableMapping):
def remove_option(self, section, option): def remove_option(self, section, option):
"""Remove an option.""" """Remove an option."""
if not section or section == self._default_section: if not section or section == self.default_section:
sectdict = self._defaults sectdict = self._defaults
else: else:
try: try:
@ -701,7 +941,7 @@ class RawConfigParser(MutableMapping):
return existed return existed
def __getitem__(self, key): 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) raise KeyError(key)
return self._proxies[key] return self._proxies[key]
@ -715,21 +955,21 @@ class RawConfigParser(MutableMapping):
self.read_dict({key: value}) self.read_dict({key: value})
def __delitem__(self, key): def __delitem__(self, key):
if key == self._default_section: if key == self.default_section:
raise ValueError("Cannot remove the default section.") raise ValueError("Cannot remove the default section.")
if not self.has_section(key): if not self.has_section(key):
raise KeyError(key) raise KeyError(key)
self.remove_section(key) self.remove_section(key)
def __contains__(self, 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): def __len__(self):
return len(self._sections) + 1 # the default section return len(self._sections) + 1 # the default section
def __iter__(self): def __iter__(self):
# XXX does it break when underlying container state changed? # 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): def _read(self, fp, fpname):
"""Parse a sectioned configuration file. """Parse a sectioned configuration file.
@ -801,7 +1041,7 @@ class RawConfigParser(MutableMapping):
lineno) lineno)
cursect = self._sections[sectname] cursect = self._sections[sectname]
elements_added.add(sectname) elements_added.add(sectname)
elif sectname == self._default_section: elif sectname == self.default_section:
cursect = self._defaults cursect = self._defaults
else: else:
cursect = self._dict() cursect = self._dict()
@ -836,7 +1076,7 @@ class RawConfigParser(MutableMapping):
cursect[optname] = [optval] cursect[optname] = [optval]
else: else:
# valueless option handling # valueless option handling
cursect[optname] = optval cursect[optname] = None
else: else:
# a non-fatal parsing error occurred. set up the # a non-fatal parsing error occurred. set up the
# exception but keep going. the exception will be # exception but keep going. the exception will be
@ -849,12 +1089,16 @@ class RawConfigParser(MutableMapping):
self._join_multiline_values() self._join_multiline_values()
def _join_multiline_values(self): def _join_multiline_values(self):
all_sections = itertools.chain((self._defaults,), defaults = self.default_section, self._defaults
self._sections.values()) all_sections = itertools.chain((defaults,),
for options in all_sections: self._sections.items())
for section, options in all_sections:
for name, val in options.items(): for name, val in options.items():
if isinstance(val, list): 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): def _handle_error(self, exc, fpname, lineno, line):
if not exc: if not exc:
@ -871,7 +1115,7 @@ class RawConfigParser(MutableMapping):
try: try:
d.update(self._sections[section]) d.update(self._sections[section])
except KeyError: except KeyError:
if section != self._default_section: if section != self.default_section:
raise NoSectionError(section) raise NoSectionError(section)
# Update with the entry specific variables # Update with the entry specific variables
if vars: if vars:
@ -906,197 +1150,31 @@ class RawConfigParser(MutableMapping):
raise TypeError("option values must be strings") raise TypeError("option values must be strings")
class ConfigParser(RawConfigParser): class ConfigParser(RawConfigParser):
"""ConfigParser implementing interpolation.""" """ConfigParser implementing interpolation."""
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): _DEFAULT_INTERPOLATION = BrokenInterpolation()
"""Get an option value for a given section.
If `vars' is provided, it must be a dictionary. The option is looked up def __init__(self, *args, **kwargs):
in `vars' (if provided), `section', and in `DEFAULTSECT' in that order. super().__init__(*args, **kwargs)
If the key is not found and `fallback' is provided, it is used as if self.__class__ is ConfigParser:
a fallback value. `None' can be provided as a `fallback' value. warnings.warn(
"The ConfigParser class will be removed in future versions."
All % interpolations are expanded in the return values, unless the " Use SafeConfigParser instead.",
optional argument `raw' is true. Values for interpolation keys are DeprecationWarning, stacklevel=2
looked up in the same manner as the option. )
Arguments `raw', `vars', and `fallback' are keyword only.
The section DEFAULT is special.
"""
try:
d = self._unify_values(section, vars)
except NoSectionError:
if fallback is _UNSET:
raise
else:
return fallback
option = self.optionxform(option)
try:
value = d[option]
except KeyError:
if fallback is _UNSET:
raise NoOptionError(option, section)
else:
return fallback
if raw or value is None:
return value
else:
return self._interpolate(section, option, value, d)
def getint(self, section, option, *, raw=False, vars=None,
fallback=_UNSET):
try:
return self._get(section, int, option, raw=raw, vars=vars)
except (NoSectionError, NoOptionError):
if fallback is _UNSET:
raise
else:
return fallback
def getfloat(self, section, option, *, raw=False, vars=None,
fallback=_UNSET):
try:
return self._get(section, float, option, raw=raw, vars=vars)
except (NoSectionError, NoOptionError):
if fallback is _UNSET:
raise
else:
return fallback
def getboolean(self, section, option, *, raw=False, vars=None,
fallback=_UNSET):
try:
return self._get(section, self._convert_to_boolean, option,
raw=raw, vars=vars)
except (NoSectionError, NoOptionError):
if fallback is _UNSET:
raise
else:
return fallback
def items(self, section, raw=False, vars=None):
"""Return a list of (name, value) tuples for each option in a section.
All % interpolations are expanded in the return values, based on the
defaults passed into the constructor, unless the optional argument
`raw' is true. Additional substitutions may be provided using the
`vars' argument, which must be a dictionary whose contents overrides
any pre-existing defaults.
The section DEFAULT is special.
"""
d = self._defaults.copy()
try:
d.update(self._sections[section])
except KeyError:
if section != self._default_section:
raise NoSectionError(section)
# Update with the entry specific variables
if vars:
for key, value in vars.items():
d[self.optionxform(key)] = value
options = list(d.keys())
if raw:
return [(option, d[option])
for option in options]
else:
return [(option, self._interpolate(section, option, d[option], d))
for option in options]
def _interpolate(self, section, option, rawval, vars):
# do the string interpolation
value = rawval
depth = MAX_INTERPOLATION_DEPTH
while depth: # Loop through this until it's done
depth -= 1
if value and "%(" in value:
value = self._KEYCRE.sub(self._interpolation_replace, value)
try:
value = value % vars
except KeyError as e:
raise InterpolationMissingOptionError(
option, section, rawval, e.args[0])
else:
break
if value and "%(" in value:
raise InterpolationDepthError(option, section, rawval)
return value
_KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
def _interpolation_replace(self, match):
s = match.group(1)
if s is None:
return match.group()
else:
return "%%(%s)s" % self.optionxform(s)
class SafeConfigParser(ConfigParser): class SafeConfigParser(ConfigParser):
"""ConfigParser implementing sane interpolation.""" """ConfigParser implementing sane interpolation."""
def _interpolate(self, section, option, rawval, vars): _DEFAULT_INTERPOLATION = BasicInterpolation()
# 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,))
def set(self, section, option, value=None): 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) self._validate_value_type(value)
# check for bad percent signs super().set(section, option, value)
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)
class SectionProxy(MutableMapping): class SectionProxy(MutableMapping):

View File

@ -4,6 +4,7 @@ import io
import os import os
import unittest import unittest
import textwrap import textwrap
import warnings
from test import support from test import support
@ -32,6 +33,7 @@ class CfgParserTestCaseClass(unittest.TestCase):
dict_type = configparser._default_dict dict_type = configparser._default_dict
strict = False strict = False
default_section = configparser.DEFAULTSECT default_section = configparser.DEFAULTSECT
interpolation = configparser._UNSET
def newconfig(self, defaults=None): def newconfig(self, defaults=None):
arguments = dict( arguments = dict(
@ -43,8 +45,12 @@ class CfgParserTestCaseClass(unittest.TestCase):
dict_type=self.dict_type, dict_type=self.dict_type,
strict=self.strict, strict=self.strict,
default_section=self.default_section, 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): def fromstring(self, string, defaults=None):
cf = self.newconfig(defaults) cf = self.newconfig(defaults)
@ -847,6 +853,70 @@ class SafeConfigParserTestCase(ConfigParserTestCase):
cf = self.newconfig() cf = self.newconfig()
self.assertRaises(ValueError, cf.add_section, self.default_section) 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): class SafeConfigParserTestCaseNonStandardDelimiters(SafeConfigParserTestCase):
delimiters = (':=', '$') delimiters = (':=', '$')
comment_prefixes = ('//', '"') comment_prefixes = ('//', '"')
@ -910,6 +980,8 @@ class Issue7005TestCase(unittest.TestCase):
def prepare(self, config_class): def prepare(self, config_class):
# This is the default, but that's the point. # This is the default, but that's the point.
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=DeprecationWarning)
cp = config_class(allow_no_value=False) cp = config_class(allow_no_value=False)
cp.add_section("section") cp.add_section("section")
cp.set("section", "option", None) cp.set("section", "option", None)
@ -978,6 +1050,7 @@ def test_main():
RawConfigParserTestCaseNonStandardDelimiters, RawConfigParserTestCaseNonStandardDelimiters,
RawConfigParserTestSambaConf, RawConfigParserTestSambaConf,
SafeConfigParserTestCase, SafeConfigParserTestCase,
SafeConfigParserTestCaseExtendedInterpolation,
SafeConfigParserTestCaseNonStandardDelimiters, SafeConfigParserTestCaseNonStandardDelimiters,
SafeConfigParserTestCaseNoValue, SafeConfigParserTestCaseNoValue,
SafeConfigParserTestCaseTrickyFile, SafeConfigParserTestCaseTrickyFile,

View File

@ -149,6 +149,14 @@ Library
- Issue #10467: Fix BytesIO.readinto() after seeking into a position after the - Issue #10467: Fix BytesIO.readinto() after seeking into a position after the
end of the file. 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 #1682942: configparser supports alternative option/value delimiters.
- Issue #5412: configparser supports mapping protocol access. - Issue #5412: configparser supports mapping protocol access.