Commit b25a7918 authored by Łukasz Langa's avatar Łukasz Langa

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.
parent ed16bf4a
...@@ -230,21 +230,18 @@ may be treated as parts of multiline values or ignored. ...@@ -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: ...@@ -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: ...@@ -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: ...@@ -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.
This is obviously dictated by backwards compatibiliy. A more predictable
approach would be to specify prefixes as ``('#', ';')`` which will allow for
both prefixes to be used in non-empty lines.
Please note that config parsers don't support escaping of comment prefixes so Comment prefixes are strings that indicate the start of a valid comment within
leaving characters out of *comment_prefixes* is a way of ensuring they can be a config file. *comment_prefixes* are used only on otherwise empty lines
used as parts of keys or values. (optionally indented) whereas *inline_comment_prefixes* can be used after
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.
* *strict*, default value: ``False`` .. versionchanged:: 3.2
In previous versions of :mod:`configparser` behaviour matched
``comment_prefixes=('#',';')`` and ``inline_comment_prefixes=(';',)``.
If set to ``True``, the parser will not allow for any section or option Please note that config parsers don't support escaping of comment prefixes so
using *inline_comment_prefixes* may prevent users from specifying option
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::
>>> 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'])
#!/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: ...@@ -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: ...@@ -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. :: ...@@ -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 ...@@ -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 ...@@ -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.
......
...@@ -15,8 +15,9 @@ ConfigParser -- responsible for parsing a list of ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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:
......
...@@ -29,6 +29,7 @@ class CfgParserTestCaseClass(unittest.TestCase): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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("""\
......
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