configparser API cleanup: default values now sensible, slightly incompatible.

Backwards compatible alternative values possible as documented.
Done by Łukasz Langa, approved by Raymond and Fred.
This commit is contained in:
Łukasz Langa 2010-12-17 01:32:29 +00:00
parent ed16bf4aaa
commit b25a791802
3 changed files with 116 additions and 63 deletions

View File

@ -230,21 +230,18 @@ may be treated as parts of multiline values or ignored.
Configuration files may include comments, prefixed by specific Configuration files may include comments, prefixed by specific
characters (``#`` and ``;`` by default [1]_). Comments may appear on characters (``#`` and ``;`` by default [1]_). Comments may appear on
their own on an otherwise empty line, or may be entered on lines holding their own on an otherwise empty line, possibly indented. [1]_
values or section names. In the latter case, they need to be preceded
by a whitespace character to be recognized as a comment. For backwards
compatibility, by default only ``;`` starts an inline comment, while
``#`` does not [1]_.
For example: For example:
.. code-block:: ini .. code-block:: ini
[Simple Values] [Simple Values]
key: value key=value
spaces in keys: allowed spaces in keys=allowed
spaces in values: allowed as well spaces in values=allowed as well
you can also use = to delimit keys from values spaces around the delimiter = obviously
you can also use : to delimit keys from values
[All Values Are Strings] [All Values Are Strings]
values like this: 1000000 values like this: 1000000
@ -261,12 +258,14 @@ For example:
key_without_value key_without_value
empty string value here = empty string value here =
[You can use comments] ; after a useful line [You can use comments]
; in an empty line # like this
after: a_value ; here's another comment ; or this
inside: a ;comment
multiline ;comment # By default only in an empty line.
value! ;comment # Inline comments can be harmful because they prevent users
# from using the delimiting characters as parts of values.
# That being said, this can be customized.
[Sections Can Be Indented] [Sections Can Be Indented]
can_values_be_as_well = True can_values_be_as_well = True
@ -509,7 +508,8 @@ the :meth:`__init__` options:
... skip-external-locking ... skip-external-locking
... old_passwords = 1 ... old_passwords = 1
... skip-bdb ... skip-bdb
... skip-innodb # we don't need ACID today ... # we don't need ACID today
... skip-innodb
... """ ... """
>>> config = configparser.ConfigParser(allow_no_value=True) >>> config = configparser.ConfigParser(allow_no_value=True)
>>> config.read_string(sample_config) >>> config.read_string(sample_config)
@ -536,28 +536,78 @@ the :meth:`__init__` options:
See also the *space_around_delimiters* argument to See also the *space_around_delimiters* argument to
:meth:`ConfigParser.write`. :meth:`ConfigParser.write`.
* *comment_prefixes*, default value: ``_COMPATIBLE`` (``'#'`` valid on empty * *comment_prefixes*, default value: ``('#', ';')``
lines, ``';'`` valid also on non-empty lines)
Comment prefixes are strings that indicate the start of a valid comment * *inline_comment_prefixes*, default value: ``None``
within a config file. The peculiar default value allows for comments starting
with ``'#'`` or ``';'`` but only the latter can be used in a non-empty line. Comment prefixes are strings that indicate the start of a valid comment within
This is obviously dictated by backwards compatibiliy. A more predictable a config file. *comment_prefixes* are used only on otherwise empty lines
approach would be to specify prefixes as ``('#', ';')`` which will allow for (optionally indented) whereas *inline_comment_prefixes* can be used after
both prefixes to be used in non-empty lines. every valid value (e.g. section names, options and empty lines as well). By
default inline comments are disabled and ``'#'`` and ``';'`` are used as
prefixes for whole line comments.
.. versionchanged:: 3.2
In previous versions of :mod:`configparser` behaviour matched
``comment_prefixes=('#',';')`` and ``inline_comment_prefixes=(';',)``.
Please note that config parsers don't support escaping of comment prefixes so Please note that config parsers don't support escaping of comment prefixes so
leaving characters out of *comment_prefixes* is a way of ensuring they can be using *inline_comment_prefixes* may prevent users from specifying option
used as parts of keys or values. values with characters used as comment prefixes. When in doubt, avoid setting
*inline_comment_prefixes*. In any circumstances, the only way of storing
comment prefix characters at the beginning of a line in multiline values is to
interpolate the prefix, for example::
* *strict*, default value: ``False`` >>> from configparser import ConfigParser, ExtendedInterpolation
>>> parser = ConfigParser(interpolation=ExtendedInterpolation())
>>> # the default BasicInterpolation could be used as well
>>> parser.read_string("""
... [DEFAULT]
... hash = #
...
... [hashes]
... shebang =
... ${hash}!/usr/bin/env python
... ${hash} -*- coding: utf-8 -*-
...
... extensions =
... enabled_extension
... another_extension
... #disabled_by_comment
... yet_another_extension
...
... interpolation not necessary = if # is not at line start
... even in multiline values = line #1
... line #2
... line #3
... """)
>>> print(parser['hashes']['shebang'])
If set to ``True``, the parser will not allow for any section or option #!/usr/bin/env python
# -*- coding: utf-8 -*-
>>> print(parser['hashes']['extensions'])
enabled_extension
another_extension
yet_another_extension
>>> print(parser['hashes']['interpolation not necessary'])
if # is not at line start
>>> print(parser['hashes']['even in multiline values'])
line #1
line #2
line #3
* *strict*, default value: ``True``
When set to ``True``, the parser will not allow for any section or option
duplicates while reading from a single source (using :meth:`read_file`, duplicates while reading from a single source (using :meth:`read_file`,
:meth:`read_string` or :meth:`read_dict`). The default is ``False`` only :meth:`read_string` or :meth:`read_dict`). It is recommended to use strict
because of backwards compatibility reasons. It is recommended to use strict
parsers in new applications. parsers in new applications.
.. versionchanged:: 3.2
In previous versions of :mod:`configparser` behaviour matched
``strict=False``.
* *empty_lines_in_values*, default value: ``True`` * *empty_lines_in_values*, default value: ``True``
In config parsers, values can span multiple lines as long as they are In config parsers, values can span multiple lines as long as they are
@ -575,7 +625,6 @@ the :meth:`__init__` options:
this = is still a part of the multiline value of 'key' this = is still a part of the multiline value of 'key'
This can be especially problematic for the user to see if she's using a This can be especially problematic for the user to see if she's using a
proportional font to edit the file. That is why when your application does proportional font to edit the file. That is why when your application does
not need values with empty lines, you should consider disallowing them. This not need values with empty lines, you should consider disallowing them. This
@ -603,8 +652,7 @@ the :meth:`__init__` options:
interpolation completely, ``ExtendedInterpolation()`` provides a more interpolation completely, ``ExtendedInterpolation()`` provides a more
advanced variant inspired by ``zc.buildout``. More on the subject in the advanced variant inspired by ``zc.buildout``. More on the subject in the
`dedicated documentation section <#interpolation-of-values>`_. `dedicated documentation section <#interpolation-of-values>`_.
:class:`RawConfigParser` has a default value of ``None``.
.. note:: :class:`RawConfigParser` is using ``None`` by default.
More advanced customization may be achieved by overriding default values of More advanced customization may be achieved by overriding default values of
@ -769,7 +817,7 @@ interpolation if an option used is not defined elsewhere. ::
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=BasicInterpolation()) .. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), inline_comment_prefixes=None, strict=True, empty_lines_in_values=True, default_section=configparser.DEFAULTSECT, interpolation=BasicInterpolation())
The main 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
@ -778,12 +826,15 @@ ConfigParser Objects
When *delimiters* is given, it is used as the set of substrings that When *delimiters* is given, it is used as the set of substrings that
divide keys from values. When *comment_prefixes* is given, it will be used divide keys from values. When *comment_prefixes* is given, it will be used
as the set of substrings that prefix comments in a line, both for the whole as the set of substrings that prefix comments in otherwise empty lines.
Comments can be indented. When *inline_comment_prefixes* is given, it will be
used as the set of substrings that prefix comments in non-empty lines.
line and inline comments. For backwards compatibility, the default value for line and inline comments. For backwards compatibility, the default value for
*comment_prefixes* is a special value that indicates that ``;`` and ``#`` can *comment_prefixes* is a special value that indicates that ``;`` and ``#`` can
start whole line comments while only ``;`` can start inline comments. start whole line comments while only ``;`` can start inline comments.
When *strict* is ``True`` (default: ``False``), the parser won't allow for When *strict* is ``True`` (the default), the parser won't allow for
any section or option duplicates while reading from a single source (file, any section or option duplicates while reading from a single source (file,
string or dictionary), raising :exc:`DuplicateSectionError` or string or dictionary), raising :exc:`DuplicateSectionError` or
:exc:`DuplicateOptionError`. When *empty_lines_in_values* is ``False`` :exc:`DuplicateOptionError`. When *empty_lines_in_values* is ``False``
@ -1043,7 +1094,7 @@ ConfigParser Objects
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) .. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), inline_comment_prefixes=None, strict=True, empty_lines_in_values=True, default_section=configaparser.DEFAULTSECT, interpolation=None)
Legacy variant of the :class:`ConfigParser` with interpolation disabled Legacy variant of the :class:`ConfigParser` with interpolation disabled
by default and unsafe ``add_section`` and ``set`` methods. by default and unsafe ``add_section`` and ``set`` methods.

View File

@ -15,8 +15,9 @@ ConfigParser -- responsible for parsing a list of
methods: methods:
__init__(defaults=None, dict_type=_default_dict, allow_no_value=False, __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, delimiters=('=', ':'), comment_prefixes=('#', ';'),
strict=False, empty_lines_in_values=True): inline_comment_prefixes=None, strict=True,
empty_lines_in_values=True):
Create the parser. When `defaults' is given, it is initialized into the Create the parser. When `defaults' is given, it is initialized into the
dictionary or intrinsic defaults. The keys must be strings, the values dictionary or intrinsic defaults. The keys must be strings, the values
must be appropriate for %()s string interpolation. must be appropriate for %()s string interpolation.
@ -29,11 +30,15 @@ ConfigParser -- responsible for parsing a list of
that divide keys from values. that divide keys from values.
When `comment_prefixes' is given, it will be used as the set of When `comment_prefixes' is given, it will be used as the set of
substrings that prefix comments in a line. substrings that prefix comments in empty lines. Comments can be
indented.
When `inline_comment_prefixes' is given, it will be used as the set of
substrings that prefix comments in non-empty lines.
When `strict` is True, the parser won't allow for any section or option When `strict` is True, the parser won't allow for any section or option
duplicates while reading from a single source (file, string or duplicates while reading from a single source (file, string or
dictionary). Default is False. dictionary). Default is True.
When `empty_lines_in_values' is False (default: True), each empty line When `empty_lines_in_values' is False (default: True), each empty line
marks the end of an option. Otherwise, internal empty lines of marks the end of an option. Otherwise, internal empty lines of
@ -340,11 +345,6 @@ class MissingSectionHeaderError(ParsingError):
self.args = (filename, lineno, line) self.args = (filename, lineno, line)
# Used in parsers to denote selecting a backwards-compatible inline comment
# character behavior (; and # are comments at the start of a line, but ; only
# inline)
_COMPATIBLE = object()
# Used in parser getters to indicate the default behaviour when a specific # Used in parser getters to indicate the default behaviour when a specific
# option is not found it to raise an exception. Created to enable `None' as # option is not found it to raise an exception. Created to enable `None' as
# a valid fallback value. # a valid fallback value.
@ -592,8 +592,8 @@ class RawConfigParser(MutableMapping):
def __init__(self, defaults=None, dict_type=_default_dict, def __init__(self, defaults=None, dict_type=_default_dict,
allow_no_value=False, *, delimiters=('=', ':'), allow_no_value=False, *, delimiters=('=', ':'),
comment_prefixes=_COMPATIBLE, strict=False, comment_prefixes=('#', ';'), inline_comment_prefixes=None,
empty_lines_in_values=True, strict=True, empty_lines_in_values=True,
default_section=DEFAULTSECT, default_section=DEFAULTSECT,
interpolation=_UNSET): interpolation=_UNSET):
@ -616,12 +616,8 @@ class RawConfigParser(MutableMapping):
else: else:
self._optcre = re.compile(self._OPT_TMPL.format(delim=d), self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
re.VERBOSE) re.VERBOSE)
if comment_prefixes is _COMPATIBLE: self._comment_prefixes = tuple(comment_prefixes or ())
self._startonly_comment_prefixes = ('#',) self._inline_comment_prefixes = tuple(inline_comment_prefixes or ())
self._comment_prefixes = (';',)
else:
self._startonly_comment_prefixes = ()
self._comment_prefixes = tuple(comment_prefixes or ())
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
@ -989,18 +985,18 @@ class RawConfigParser(MutableMapping):
indent_level = 0 indent_level = 0
e = None # None, or an exception e = None # None, or an exception
for lineno, line in enumerate(fp, start=1): for lineno, line in enumerate(fp, start=1):
# strip full line comments
comment_start = None comment_start = None
for prefix in self._startonly_comment_prefixes:
if line.strip().startswith(prefix):
comment_start = 0
break
# strip inline comments # strip inline comments
for prefix in self._comment_prefixes: for prefix in self._inline_comment_prefixes:
index = line.find(prefix) index = line.find(prefix)
if index == 0 or (index > 0 and line[index-1].isspace()): if index == 0 or (index > 0 and line[index-1].isspace()):
comment_start = index comment_start = index
break break
# strip full line comments
for prefix in self._comment_prefixes:
if line.strip().startswith(prefix):
comment_start = 0
break
value = line[:comment_start].strip() value = line[:comment_start].strip()
if not value: if not value:
if self._empty_lines_in_values: if self._empty_lines_in_values:

View File

@ -29,6 +29,7 @@ class CfgParserTestCaseClass(unittest.TestCase):
allow_no_value = False allow_no_value = False
delimiters = ('=', ':') delimiters = ('=', ':')
comment_prefixes = (';', '#') comment_prefixes = (';', '#')
inline_comment_prefixes = (';', '#')
empty_lines_in_values = True empty_lines_in_values = True
dict_type = configparser._default_dict dict_type = configparser._default_dict
strict = False strict = False
@ -41,6 +42,7 @@ class CfgParserTestCaseClass(unittest.TestCase):
allow_no_value=self.allow_no_value, allow_no_value=self.allow_no_value,
delimiters=self.delimiters, delimiters=self.delimiters,
comment_prefixes=self.comment_prefixes, comment_prefixes=self.comment_prefixes,
inline_comment_prefixes=self.inline_comment_prefixes,
empty_lines_in_values=self.empty_lines_in_values, empty_lines_in_values=self.empty_lines_in_values,
dict_type=self.dict_type, dict_type=self.dict_type,
strict=self.strict, strict=self.strict,
@ -812,6 +814,7 @@ class ConfigParserTestCaseLegacyInterpolation(ConfigParserTestCase):
class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase): class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase):
delimiters = (':=', '$') delimiters = (':=', '$')
comment_prefixes = ('//', '"') comment_prefixes = ('//', '"')
inline_comment_prefixes = ('//', '"')
class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase): class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase):
default_section = 'general' default_section = 'general'
@ -888,10 +891,12 @@ class RawConfigParserTestCase(BasicTestCase):
class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
delimiters = (':=', '$') delimiters = (':=', '$')
comment_prefixes = ('//', '"') comment_prefixes = ('//', '"')
inline_comment_prefixes = ('//', '"')
class RawConfigParserTestSambaConf(BasicTestCase): class RawConfigParserTestSambaConf(CfgParserTestCaseClass):
config_class = configparser.RawConfigParser config_class = configparser.RawConfigParser
comment_prefixes = ('#', ';', '//', '----') comment_prefixes = ('#', ';', '----')
inline_comment_prefixes = ('//',)
empty_lines_in_values = False empty_lines_in_values = False
def test_reading(self): def test_reading(self):
@ -1074,7 +1079,8 @@ class SortedTestCase(RawConfigParserTestCase):
class CompatibleTestCase(CfgParserTestCaseClass): class CompatibleTestCase(CfgParserTestCaseClass):
config_class = configparser.RawConfigParser config_class = configparser.RawConfigParser
comment_prefixes = configparser._COMPATIBLE comment_prefixes = '#;'
inline_comment_prefixes = ';'
def test_comment_handling(self): def test_comment_handling(self):
config_string = textwrap.dedent("""\ config_string = textwrap.dedent("""\