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
characters (``#`` and ``;`` by default [1]_). Comments may appear on
their own on an otherwise empty line, or may be entered on lines holding
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]_.
their own on an otherwise empty line, possibly indented. [1]_
For example:
.. code-block:: ini
[Simple Values]
key: value
spaces in keys: allowed
spaces in values: allowed as well
you can also use = to delimit keys from values
key=value
spaces in keys=allowed
spaces in values=allowed as well
spaces around the delimiter = obviously
you can also use : to delimit keys from values
[All Values Are Strings]
values like this: 1000000
@ -261,12 +258,14 @@ For example:
key_without_value
empty string value here =
[You can use comments] ; after a useful line
; in an empty line
after: a_value ; here's another comment
inside: a ;comment
multiline ;comment
value! ;comment
[You can use comments]
# like this
; or this
# By default only in an empty line.
# 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]
can_values_be_as_well = True
@ -509,7 +508,8 @@ the :meth:`__init__` options:
... skip-external-locking
... old_passwords = 1
... 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.read_string(sample_config)
@ -536,28 +536,78 @@ the :meth:`__init__` options:
See also the *space_around_delimiters* argument to
:meth:`ConfigParser.write`.
* *comment_prefixes*, default value: ``_COMPATIBLE`` (``'#'`` valid on empty
lines, ``';'`` valid also on non-empty lines)
* *comment_prefixes*, default value: ``('#', ';')``
Comment prefixes are strings that indicate the start of a valid comment
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.
* *inline_comment_prefixes*, default value: ``None``
Comment prefixes are strings that indicate the start of a valid comment within
a config file. *comment_prefixes* are used only on otherwise empty lines
(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.
.. 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
leaving characters out of *comment_prefixes* is a way of ensuring they can be
used as parts of keys or values.
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::
* *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`,
:meth:`read_string` or :meth:`read_dict`). The default is ``False`` only
because of backwards compatibility reasons. It is recommended to use strict
:meth:`read_string` or :meth:`read_dict`). It is recommended to use strict
parsers in new applications.
.. versionchanged:: 3.2
In previous versions of :mod:`configparser` behaviour matched
``strict=False``.
* *empty_lines_in_values*, default value: ``True``
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 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
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
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.
:class:`RawConfigParser` has a default value of ``None``.
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
--------------------
.. 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
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
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
*comment_prefixes* is a special value that indicates that ``;`` and ``#`` can
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,
string or dictionary), raising :exc:`DuplicateSectionError` or
:exc:`DuplicateOptionError`. When *empty_lines_in_values* is ``False``
@ -1043,7 +1094,7 @@ ConfigParser 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
by default and unsafe ``add_section`` and ``set`` methods.

View File

@ -15,8 +15,9 @@ ConfigParser -- responsible for parsing a list of
methods:
__init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
delimiters=('=', ':'), comment_prefixes=_COMPATIBLE,
strict=False, empty_lines_in_values=True):
delimiters=('=', ':'), comment_prefixes=('#', ';'),
inline_comment_prefixes=None, strict=True,
empty_lines_in_values=True):
Create the parser. When `defaults' is given, it is initialized into the
dictionary or intrinsic defaults. The keys must be strings, the values
must be appropriate for %()s string interpolation.
@ -29,11 +30,15 @@ ConfigParser -- responsible for parsing a list of
that divide keys from values.
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
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
marks the end of an option. Otherwise, internal empty lines of
@ -340,11 +345,6 @@ class MissingSectionHeaderError(ParsingError):
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
# option is not found it to raise an exception. Created to enable `None' as
# a valid fallback value.
@ -592,8 +592,8 @@ class RawConfigParser(MutableMapping):
def __init__(self, defaults=None, dict_type=_default_dict,
allow_no_value=False, *, delimiters=('=', ':'),
comment_prefixes=_COMPATIBLE, strict=False,
empty_lines_in_values=True,
comment_prefixes=('#', ';'), inline_comment_prefixes=None,
strict=True, empty_lines_in_values=True,
default_section=DEFAULTSECT,
interpolation=_UNSET):
@ -616,12 +616,8 @@ class RawConfigParser(MutableMapping):
else:
self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
re.VERBOSE)
if comment_prefixes is _COMPATIBLE:
self._startonly_comment_prefixes = ('#',)
self._comment_prefixes = (';',)
else:
self._startonly_comment_prefixes = ()
self._comment_prefixes = tuple(comment_prefixes or ())
self._comment_prefixes = tuple(comment_prefixes or ())
self._inline_comment_prefixes = tuple(inline_comment_prefixes or ())
self._strict = strict
self._allow_no_value = allow_no_value
self._empty_lines_in_values = empty_lines_in_values
@ -989,18 +985,18 @@ class RawConfigParser(MutableMapping):
indent_level = 0
e = None # None, or an exception
for lineno, line in enumerate(fp, start=1):
# strip full line comments
comment_start = None
for prefix in self._startonly_comment_prefixes:
if line.strip().startswith(prefix):
comment_start = 0
break
# strip inline comments
for prefix in self._comment_prefixes:
for prefix in self._inline_comment_prefixes:
index = line.find(prefix)
if index == 0 or (index > 0 and line[index-1].isspace()):
comment_start = index
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()
if not value:
if self._empty_lines_in_values:

View File

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