issue #9452:
Add read_file, read_string, and read_dict to the configparser API; new source attribute to exceptions.
This commit is contained in:
parent
f14c263280
commit
a492362f9a
|
@ -27,8 +27,9 @@ customized by end users easily.
|
||||||
the Windows Registry extended version of INI syntax.
|
the Windows Registry extended version of INI syntax.
|
||||||
|
|
||||||
A configuration file consists of sections, each led by a ``[section]`` header,
|
A configuration file consists of sections, each led by a ``[section]`` header,
|
||||||
followed by name/value entries separated by a specific string (``=`` or ``:`` by
|
followed by key/value entries separated by a specific string (``=`` or ``:`` by
|
||||||
default). Note that leading whitespace is removed from values. Values can be
|
default). By default, section names are case sensitive but keys are not. Leading
|
||||||
|
und trailing whitespace is removed from keys and from values. Values can be
|
||||||
ommitted, in which case the key/value delimiter may also be left out. Values
|
ommitted, in which case the key/value delimiter may also be left out. Values
|
||||||
can also span multiple lines, as long as they are indented deeper than the first
|
can also span multiple lines, as long as they are indented deeper than the first
|
||||||
line of the value. Depending on the parser's mode, blank lines may be treated
|
line of the value. Depending on the parser's mode, blank lines may be treated
|
||||||
|
@ -101,7 +102,7 @@ that sorts its keys, the sections will be sorted on write-back, as will be the
|
||||||
keys within each section.
|
keys within each section.
|
||||||
|
|
||||||
|
|
||||||
.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, empty_lines_in_values=True, allow_no_value=False)
|
.. class:: RawConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=_COMPATIBLE, strict=False, empty_lines_in_values=True)
|
||||||
|
|
||||||
The basic configuration object. When *defaults* is given, it is initialized
|
The basic configuration object. 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
|
||||||
|
@ -115,11 +116,14 @@ keys within each section.
|
||||||
*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 *empty_lines_in_values* is ``False`` (default: ``True``), each empty
|
When *strict* is ``True`` (default: ``False``), the parser won't allow for
|
||||||
line marks the end of an option. Otherwise, internal empty lines of a
|
any section or option duplicates while reading from a single source (file,
|
||||||
multiline option are kept as part of the value. When *allow_no_value* is
|
string or dictionary), raising :exc:`DuplicateSectionError` or
|
||||||
true (default: ``False``), options without values are accepted; the value
|
:exc:`DuplicateOptionError`. When *empty_lines_in_values* is ``False``
|
||||||
presented for these is ``None``.
|
(default: ``True``), each empty line marks the end of an option. Otherwise,
|
||||||
|
internal empty lines of a multiline option are kept as part of the value.
|
||||||
|
When *allow_no_value* is ``True`` (default: ``False``), options without
|
||||||
|
values are accepted; the value presented for these is ``None``.
|
||||||
|
|
||||||
This class does not support the magical interpolation behavior.
|
This class does not support the magical interpolation behavior.
|
||||||
|
|
||||||
|
@ -127,11 +131,11 @@ keys within each section.
|
||||||
The default *dict_type* is :class:`collections.OrderedDict`.
|
The default *dict_type* is :class:`collections.OrderedDict`.
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
.. versionchanged:: 3.2
|
||||||
*delimiters*, *comment_prefixes*, *empty_lines_in_values* and
|
*allow_no_value*, *delimiters*, *comment_prefixes*, *strict* and
|
||||||
*allow_no_value* were added.
|
*empty_lines_in_values* were added.
|
||||||
|
|
||||||
|
|
||||||
.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=('#', ';'), empty_lines_in_values=True, allow_no_value=False)
|
.. class:: SafeConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), strict=False, empty_lines_in_values=True)
|
||||||
|
|
||||||
Derived class of :class:`ConfigParser` that implements a sane variant of the
|
Derived class of :class:`ConfigParser` that implements a sane variant of the
|
||||||
magical interpolation feature. This implementation is more predictable as it
|
magical interpolation feature. This implementation is more predictable as it
|
||||||
|
@ -147,11 +151,11 @@ keys within each section.
|
||||||
The default *dict_type* is :class:`collections.OrderedDict`.
|
The default *dict_type* is :class:`collections.OrderedDict`.
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
.. versionchanged:: 3.2
|
||||||
*delimiters*, *comment_prefixes*, *empty_lines_in_values* and
|
*allow_no_value*, *delimiters*, *comment_prefixes*, *strict* and
|
||||||
*allow_no_value* were added.
|
*empty_lines_in_values* were added.
|
||||||
|
|
||||||
|
|
||||||
.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, delimiters=('=', ':'), comment_prefixes=('#', ';'), empty_lines_in_values=True, allow_no_value=False)
|
.. class:: ConfigParser(defaults=None, dict_type=collections.OrderedDict, allow_no_value=False, delimiters=('=', ':'), comment_prefixes=('#', ';'), strict=False, empty_lines_in_values=True)
|
||||||
|
|
||||||
Derived class of :class:`RawConfigParser` that implements the magical
|
Derived class of :class:`RawConfigParser` that implements the magical
|
||||||
interpolation feature and adds optional arguments to the :meth:`get` and
|
interpolation feature and adds optional arguments to the :meth:`get` and
|
||||||
|
@ -174,8 +178,8 @@ keys within each section.
|
||||||
The default *dict_type* is :class:`collections.OrderedDict`.
|
The default *dict_type* is :class:`collections.OrderedDict`.
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
.. versionchanged:: 3.2
|
||||||
*delimiters*, *comment_prefixes*, *empty_lines_in_values* and
|
*allow_no_value*, *delimiters*, *comment_prefixes*,
|
||||||
*allow_no_value* were added.
|
*strict* and *empty_lines_in_values* were added.
|
||||||
|
|
||||||
|
|
||||||
.. exception:: Error
|
.. exception:: Error
|
||||||
|
@ -191,12 +195,26 @@ keys within each section.
|
||||||
.. exception:: DuplicateSectionError
|
.. exception:: DuplicateSectionError
|
||||||
|
|
||||||
Exception raised if :meth:`add_section` is called with the name of a section
|
Exception raised if :meth:`add_section` is called with the name of a section
|
||||||
that is already present.
|
that is already present or in strict parsers when a section if found more
|
||||||
|
than once in a single input file, string or dictionary.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
Optional ``source`` and ``lineno`` attributes and arguments to
|
||||||
|
:meth:`__init__` were added.
|
||||||
|
|
||||||
|
|
||||||
|
.. exception:: DuplicateOptionError
|
||||||
|
|
||||||
|
Exception raised by strict parsers if a single option appears twice during
|
||||||
|
reading from a single file, string or dictionary. This catches misspellings
|
||||||
|
and case sensitivity-related errors, e.g. a dictionary may have two keys
|
||||||
|
representing the same case-insensitive configuration key.
|
||||||
|
|
||||||
|
|
||||||
.. exception:: NoOptionError
|
.. exception:: NoOptionError
|
||||||
|
|
||||||
Exception raised when a specified option is not found in the specified section.
|
Exception raised when a specified option is not found in the specified
|
||||||
|
section.
|
||||||
|
|
||||||
|
|
||||||
.. exception:: InterpolationError
|
.. exception:: InterpolationError
|
||||||
|
@ -233,6 +251,9 @@ keys within each section.
|
||||||
|
|
||||||
Exception raised when errors occur attempting to parse a file.
|
Exception raised when errors occur attempting to parse a file.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.2
|
||||||
|
The ``filename`` attribute and :meth:`__init__` argument were renamed to
|
||||||
|
``source`` for consistency.
|
||||||
|
|
||||||
.. data:: MAX_INTERPOLATION_DEPTH
|
.. data:: MAX_INTERPOLATION_DEPTH
|
||||||
|
|
||||||
|
@ -315,15 +336,41 @@ RawConfigParser Objects
|
||||||
default encoding for :func:`open`.
|
default encoding for :func:`open`.
|
||||||
|
|
||||||
|
|
||||||
.. method:: RawConfigParser.readfp(fp, filename=None)
|
.. method:: RawConfigParser.read_file(f, source=None)
|
||||||
|
|
||||||
Read and parse configuration data from the file or file-like object in *fp*
|
Read and parse configuration data from the file or file-like object in *f*
|
||||||
(only the :meth:`readline` method is used). The file-like object must
|
(only the :meth:`readline` method is used). The file-like object must
|
||||||
operate in text mode, i.e. return strings from :meth:`readline`.
|
operate in text mode, i.e. return strings from :meth:`readline`.
|
||||||
|
|
||||||
If *filename* is omitted and *fp* has a :attr:`name` attribute, that is used
|
Optional argument *source* specifies the name of the file being read. It not
|
||||||
for *filename*; the default is ``<???>``.
|
given and *f* has a :attr:`name` attribute, that is used for *source*; the
|
||||||
|
default is ``<???>``.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
Renamed from :meth:`readfp` (with the ``filename`` attribute renamed to
|
||||||
|
``source`` for consistency with other ``read_*`` methods).
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: RawConfigParser.read_string(string, source='<string>')
|
||||||
|
|
||||||
|
Parse configuration data from a given string.
|
||||||
|
|
||||||
|
Optional argument *source* specifies a context-specific name of the string
|
||||||
|
passed. If not given, ``<string>`` is used.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
.. method:: RawConfigParser.read_dict(dictionary, source='<dict>')
|
||||||
|
|
||||||
|
Load configuration from a dictionary. Keys are section names, values are
|
||||||
|
dictionaries with keys and values that should be present in the section. If
|
||||||
|
the used dictionary type preserves order, sections and their keys will be
|
||||||
|
added in order.
|
||||||
|
|
||||||
|
Optional argument *source* specifies a context-specific name of the
|
||||||
|
dictionary passed. If not given, ``<dict>`` is used.
|
||||||
|
|
||||||
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
.. method:: RawConfigParser.get(section, option)
|
.. method:: RawConfigParser.get(section, option)
|
||||||
|
|
||||||
|
@ -408,6 +455,10 @@ RawConfigParser Objects
|
||||||
Note that when reading configuration files, whitespace around the
|
Note that when reading configuration files, whitespace around the
|
||||||
option names are stripped before :meth:`optionxform` is called.
|
option names are stripped before :meth:`optionxform` is called.
|
||||||
|
|
||||||
|
.. method:: RawConfigParser.readfp(fp, filename=None)
|
||||||
|
|
||||||
|
.. deprecated:: 3.2
|
||||||
|
Please use :meth:`read_file` instead.
|
||||||
|
|
||||||
.. _configparser-objects:
|
.. _configparser-objects:
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,10 @@ ConfigParser -- responsible for parsing a list of
|
||||||
|
|
||||||
__init__(defaults=None, dict_type=_default_dict,
|
__init__(defaults=None, dict_type=_default_dict,
|
||||||
delimiters=('=', ':'), comment_prefixes=('#', ';'),
|
delimiters=('=', ':'), comment_prefixes=('#', ';'),
|
||||||
empty_lines_in_values=True, allow_no_value=False):
|
strict=False, empty_lines_in_values=True, allow_no_value=False):
|
||||||
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. Note that `__name__'
|
must be appropriate for %()s string interpolation. Note that `__name__'
|
||||||
is always an intrinsic default; its value is the section's name.
|
is always an intrinsic default; its value is the section's name.
|
||||||
|
|
||||||
When `dict_type' is given, it will be used to create the dictionary
|
When `dict_type' is given, it will be used to create the dictionary
|
||||||
|
@ -42,6 +42,10 @@ ConfigParser -- responsible for parsing a list of
|
||||||
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 a line.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
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
|
||||||
a multiline option are kept as part of the value.
|
a multiline option are kept as part of the value.
|
||||||
|
@ -66,10 +70,19 @@ ConfigParser -- responsible for parsing a list of
|
||||||
name. A single filename is also allowed. Non-existing files
|
name. A single filename is also allowed. Non-existing files
|
||||||
are ignored. Return list of successfully read files.
|
are ignored. Return list of successfully read files.
|
||||||
|
|
||||||
readfp(fp, filename=None)
|
read_file(f, filename=None)
|
||||||
Read and parse one configuration file, given as a file object.
|
Read and parse one configuration file, given as a file object.
|
||||||
The filename defaults to fp.name; it is only used in error
|
The filename defaults to f.name; it is only used in error
|
||||||
messages (if fp has no `name' attribute, the string `<???>' is used).
|
messages (if f has no `name' attribute, the string `<???>' is used).
|
||||||
|
|
||||||
|
read_string(string)
|
||||||
|
Read configuration from a given string.
|
||||||
|
|
||||||
|
read_dict(dictionary)
|
||||||
|
Read configuration from a dictionary. Keys are section names,
|
||||||
|
values are dictionaries with keys and values that should be present
|
||||||
|
in the section. If the used dictionary type preserves order, sections
|
||||||
|
and their keys will be added in order.
|
||||||
|
|
||||||
get(section, option, raw=False, vars=None)
|
get(section, option, raw=False, vars=None)
|
||||||
Return a string value for the named option. All % interpolations are
|
Return a string value for the named option. All % interpolations are
|
||||||
|
@ -114,11 +127,13 @@ except ImportError:
|
||||||
# fallback for setup.py which hasn't yet built _collections
|
# fallback for setup.py which hasn't yet built _collections
|
||||||
_default_dict = dict
|
_default_dict = dict
|
||||||
|
|
||||||
|
import io
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
__all__ = ["NoSectionError", "DuplicateSectionError", "NoOptionError",
|
__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
|
||||||
"InterpolationError", "InterpolationDepthError",
|
"NoOptionError", "InterpolationError", "InterpolationDepthError",
|
||||||
"InterpolationSyntaxError", "ParsingError",
|
"InterpolationSyntaxError", "ParsingError",
|
||||||
"MissingSectionHeaderError",
|
"MissingSectionHeaderError",
|
||||||
"ConfigParser", "SafeConfigParser", "RawConfigParser",
|
"ConfigParser", "SafeConfigParser", "RawConfigParser",
|
||||||
|
@ -147,8 +162,8 @@ class Error(Exception):
|
||||||
self.__message = value
|
self.__message = value
|
||||||
|
|
||||||
# BaseException.message has been deprecated since Python 2.6. To prevent
|
# BaseException.message has been deprecated since Python 2.6. To prevent
|
||||||
# DeprecationWarning from popping up over this pre-existing attribute, use a
|
# DeprecationWarning from popping up over this pre-existing attribute, use
|
||||||
# new property that takes lookup precedence.
|
# a new property that takes lookup precedence.
|
||||||
message = property(_get_message, _set_message)
|
message = property(_get_message, _set_message)
|
||||||
|
|
||||||
def __init__(self, msg=''):
|
def __init__(self, msg=''):
|
||||||
|
@ -171,12 +186,56 @@ class NoSectionError(Error):
|
||||||
|
|
||||||
|
|
||||||
class DuplicateSectionError(Error):
|
class DuplicateSectionError(Error):
|
||||||
"""Raised when a section is multiply-created."""
|
"""Raised when a section is repeated in an input source.
|
||||||
|
|
||||||
def __init__(self, section):
|
Possible repetitions that raise this exception are: multiple creation
|
||||||
Error.__init__(self, "Section %r already exists" % section)
|
using the API or in strict parsers when a section is found more than once
|
||||||
|
in a single input file, string or dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, section, source=None, lineno=None):
|
||||||
|
msg = [repr(section), " already exists"]
|
||||||
|
if source is not None:
|
||||||
|
message = ["While reading from ", source]
|
||||||
|
if lineno is not None:
|
||||||
|
message.append(" [line {0:2d}]".format(lineno))
|
||||||
|
message.append(": section ")
|
||||||
|
message.extend(msg)
|
||||||
|
msg = message
|
||||||
|
else:
|
||||||
|
msg.insert(0, "Section ")
|
||||||
|
Error.__init__(self, "".join(msg))
|
||||||
self.section = section
|
self.section = section
|
||||||
self.args = (section, )
|
self.source = source
|
||||||
|
self.lineno = lineno
|
||||||
|
self.args = (section, source, lineno)
|
||||||
|
|
||||||
|
|
||||||
|
class DuplicateOptionError(Error):
|
||||||
|
"""Raised by strict parsers when an option is repeated in an input source.
|
||||||
|
|
||||||
|
Current implementation raises this exception only when an option is found
|
||||||
|
more than once in a single file, string or dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, section, option, source=None, lineno=None):
|
||||||
|
msg = [repr(option), " in section ", repr(section),
|
||||||
|
" already exists"]
|
||||||
|
if source is not None:
|
||||||
|
message = ["While reading from ", source]
|
||||||
|
if lineno is not None:
|
||||||
|
message.append(" [line {0:2d}]".format(lineno))
|
||||||
|
message.append(": option ")
|
||||||
|
message.extend(msg)
|
||||||
|
msg = message
|
||||||
|
else:
|
||||||
|
msg.insert(0, "Option ")
|
||||||
|
Error.__init__(self, "".join(msg))
|
||||||
|
self.section = section
|
||||||
|
self.option = option
|
||||||
|
self.source = source
|
||||||
|
self.lineno = lineno
|
||||||
|
self.args = (section, option, source, lineno)
|
||||||
|
|
||||||
|
|
||||||
class NoOptionError(Error):
|
class NoOptionError(Error):
|
||||||
|
@ -216,8 +275,12 @@ class InterpolationMissingOptionError(InterpolationError):
|
||||||
|
|
||||||
|
|
||||||
class InterpolationSyntaxError(InterpolationError):
|
class InterpolationSyntaxError(InterpolationError):
|
||||||
"""Raised when the source text into which substitutions are made
|
"""Raised when the source text contains invalid syntax.
|
||||||
does not conform to the required syntax."""
|
|
||||||
|
Current implementation raises this exception only for SafeConfigParser
|
||||||
|
instances when the source text into which substitutions are made
|
||||||
|
does not conform to the required syntax.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class InterpolationDepthError(InterpolationError):
|
class InterpolationDepthError(InterpolationError):
|
||||||
|
@ -236,11 +299,40 @@ class InterpolationDepthError(InterpolationError):
|
||||||
class ParsingError(Error):
|
class ParsingError(Error):
|
||||||
"""Raised when a configuration file does not follow legal syntax."""
|
"""Raised when a configuration file does not follow legal syntax."""
|
||||||
|
|
||||||
def __init__(self, filename):
|
def __init__(self, source=None, filename=None):
|
||||||
Error.__init__(self, 'File contains parsing errors: %s' % filename)
|
# Exactly one of `source'/`filename' arguments has to be given.
|
||||||
self.filename = filename
|
# `filename' kept for compatibility.
|
||||||
|
if filename and source:
|
||||||
|
raise ValueError("Cannot specify both `filename' and `source'. "
|
||||||
|
"Use `source'.")
|
||||||
|
elif not filename and not source:
|
||||||
|
raise ValueError("Required argument `source' not given.")
|
||||||
|
elif filename:
|
||||||
|
source = filename
|
||||||
|
Error.__init__(self, 'Source contains parsing errors: %s' % source)
|
||||||
|
self.source = source
|
||||||
self.errors = []
|
self.errors = []
|
||||||
self.args = (filename, )
|
self.args = (source, )
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filename(self):
|
||||||
|
"""Deprecated, use `source'."""
|
||||||
|
warnings.warn(
|
||||||
|
"This 'filename' attribute will be removed in future versions. "
|
||||||
|
"Use 'source' instead.",
|
||||||
|
PendingDeprecationWarning, stacklevel=2
|
||||||
|
)
|
||||||
|
return self.source
|
||||||
|
|
||||||
|
@filename.setter
|
||||||
|
def filename(self, value):
|
||||||
|
"""Deprecated, user `source'."""
|
||||||
|
warnings.warn(
|
||||||
|
"The 'filename' attribute will be removed in future versions. "
|
||||||
|
"Use 'source' instead.",
|
||||||
|
PendingDeprecationWarning, stacklevel=2
|
||||||
|
)
|
||||||
|
self.source = value
|
||||||
|
|
||||||
def append(self, lineno, line):
|
def append(self, lineno, line):
|
||||||
self.errors.append((lineno, line))
|
self.errors.append((lineno, line))
|
||||||
|
@ -255,7 +347,7 @@ class MissingSectionHeaderError(ParsingError):
|
||||||
self,
|
self,
|
||||||
'File contains no section headers.\nfile: %s, line: %d\n%r' %
|
'File contains no section headers.\nfile: %s, line: %d\n%r' %
|
||||||
(filename, lineno, line))
|
(filename, lineno, line))
|
||||||
self.filename = filename
|
self.source = filename
|
||||||
self.lineno = lineno
|
self.lineno = lineno
|
||||||
self.line = line
|
self.line = line
|
||||||
self.args = (filename, lineno, line)
|
self.args = (filename, lineno, line)
|
||||||
|
@ -302,8 +394,9 @@ class RawConfigParser:
|
||||||
_COMPATIBLE = object()
|
_COMPATIBLE = object()
|
||||||
|
|
||||||
def __init__(self, defaults=None, dict_type=_default_dict,
|
def __init__(self, defaults=None, dict_type=_default_dict,
|
||||||
delimiters=('=', ':'), comment_prefixes=_COMPATIBLE,
|
allow_no_value=False, *, delimiters=('=', ':'),
|
||||||
empty_lines_in_values=True, allow_no_value=False):
|
comment_prefixes=_COMPATIBLE, strict=False,
|
||||||
|
empty_lines_in_values=True):
|
||||||
self._dict = dict_type
|
self._dict = dict_type
|
||||||
self._sections = self._dict()
|
self._sections = self._dict()
|
||||||
self._defaults = self._dict()
|
self._defaults = self._dict()
|
||||||
|
@ -314,12 +407,12 @@ class RawConfigParser:
|
||||||
if delimiters == ('=', ':'):
|
if delimiters == ('=', ':'):
|
||||||
self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
|
self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
|
||||||
else:
|
else:
|
||||||
delim = "|".join(re.escape(d) for d in delimiters)
|
d = "|".join(re.escape(d) for d in delimiters)
|
||||||
if allow_no_value:
|
if allow_no_value:
|
||||||
self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=delim),
|
self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d),
|
||||||
re.VERBOSE)
|
re.VERBOSE)
|
||||||
else:
|
else:
|
||||||
self._optcre = re.compile(self._OPT_TMPL.format(delim=delim),
|
self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
|
||||||
re.VERBOSE)
|
re.VERBOSE)
|
||||||
if comment_prefixes is self._COMPATIBLE:
|
if comment_prefixes is self._COMPATIBLE:
|
||||||
self._startonly_comment_prefixes = ('#',)
|
self._startonly_comment_prefixes = ('#',)
|
||||||
|
@ -327,6 +420,7 @@ class RawConfigParser:
|
||||||
else:
|
else:
|
||||||
self._startonly_comment_prefixes = ()
|
self._startonly_comment_prefixes = ()
|
||||||
self._comment_prefixes = tuple(comment_prefixes or ())
|
self._comment_prefixes = tuple(comment_prefixes or ())
|
||||||
|
self._strict = strict
|
||||||
self._empty_lines_in_values = empty_lines_in_values
|
self._empty_lines_in_values = empty_lines_in_values
|
||||||
|
|
||||||
def defaults(self):
|
def defaults(self):
|
||||||
|
@ -394,20 +488,59 @@ class RawConfigParser:
|
||||||
read_ok.append(filename)
|
read_ok.append(filename)
|
||||||
return read_ok
|
return read_ok
|
||||||
|
|
||||||
def readfp(self, fp, filename=None):
|
def read_file(self, f, source=None):
|
||||||
"""Like read() but the argument must be a file-like object.
|
"""Like read() but the argument must be a file-like object.
|
||||||
|
|
||||||
The `fp' argument must have a `readline' method. Optional
|
The `f' argument must have a `readline' method. Optional second
|
||||||
second argument is the `filename', which if not given, is
|
argument is the `source' specifying the name of the file being read. If
|
||||||
taken from fp.name. If fp has no `name' attribute, `<???>' is
|
not given, it is taken from f.name. If `f' has no `name' attribute,
|
||||||
used.
|
`<???>' is used.
|
||||||
"""
|
"""
|
||||||
if filename is None:
|
if source is None:
|
||||||
try:
|
try:
|
||||||
filename = fp.name
|
srouce = f.name
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
filename = '<???>'
|
source = '<???>'
|
||||||
self._read(fp, filename)
|
self._read(f, source)
|
||||||
|
|
||||||
|
def read_string(self, string, source='<string>'):
|
||||||
|
"""Read configuration from a given string."""
|
||||||
|
sfile = io.StringIO(string)
|
||||||
|
self.read_file(sfile, source)
|
||||||
|
|
||||||
|
def read_dict(self, dictionary, source='<dict>'):
|
||||||
|
"""Read configuration from a dictionary.
|
||||||
|
|
||||||
|
Keys are section names, values are dictionaries with keys and values
|
||||||
|
that should be present in the section. If the used dictionary type
|
||||||
|
preserves order, sections and their keys will be added in order.
|
||||||
|
|
||||||
|
Optional second argument is the `source' specifying the name of the
|
||||||
|
dictionary being read.
|
||||||
|
"""
|
||||||
|
elements_added = set()
|
||||||
|
for section, keys in dictionary.items():
|
||||||
|
try:
|
||||||
|
self.add_section(section)
|
||||||
|
except DuplicateSectionError:
|
||||||
|
if self._strict and section in elements_added:
|
||||||
|
raise
|
||||||
|
elements_added.add(section)
|
||||||
|
for key, value in keys.items():
|
||||||
|
key = self.optionxform(key)
|
||||||
|
if self._strict and (section, key) in elements_added:
|
||||||
|
raise DuplicateOptionError(section, key, source)
|
||||||
|
elements_added.add((section, key))
|
||||||
|
self.set(section, key, value)
|
||||||
|
|
||||||
|
def readfp(self, fp, filename=None):
|
||||||
|
"""Deprecated, use read_file instead."""
|
||||||
|
warnings.warn(
|
||||||
|
"This method will be removed in future versions. "
|
||||||
|
"Use 'parser.read_file()' instead.",
|
||||||
|
PendingDeprecationWarning, stacklevel=2
|
||||||
|
)
|
||||||
|
self.read_file(fp, source=filename)
|
||||||
|
|
||||||
def get(self, section, option):
|
def get(self, section, option):
|
||||||
opt = self.optionxform(option)
|
opt = self.optionxform(option)
|
||||||
|
@ -461,7 +594,6 @@ class RawConfigParser:
|
||||||
|
|
||||||
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 == DEFAULTSECT:
|
if not section or section == DEFAULTSECT:
|
||||||
option = self.optionxform(option)
|
option = self.optionxform(option)
|
||||||
return option in self._defaults
|
return option in self._defaults
|
||||||
|
@ -474,7 +606,6 @@ class RawConfigParser:
|
||||||
|
|
||||||
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 == DEFAULTSECT:
|
if not section or section == DEFAULTSECT:
|
||||||
sectdict = self._defaults
|
sectdict = self._defaults
|
||||||
else:
|
else:
|
||||||
|
@ -538,21 +669,23 @@ class RawConfigParser:
|
||||||
def _read(self, fp, fpname):
|
def _read(self, fp, fpname):
|
||||||
"""Parse a sectioned configuration file.
|
"""Parse a sectioned configuration file.
|
||||||
|
|
||||||
Each section in a configuration file contains a header, indicated by a
|
Each section in a configuration file contains a header, indicated by
|
||||||
name in square brackets (`[]'), plus key/value options, indicated by
|
a name in square brackets (`[]'), plus key/value options, indicated by
|
||||||
`name' and `value' delimited with a specific substring (`=' or `:' by
|
`name' and `value' delimited with a specific substring (`=' or `:' by
|
||||||
default).
|
default).
|
||||||
|
|
||||||
Values can span multiple lines, as long as they are indented deeper than
|
Values can span multiple lines, as long as they are indented deeper
|
||||||
the first line of the value. Depending on the parser's mode, blank lines
|
than the first line of the value. Depending on the parser's mode, blank
|
||||||
may be treated as parts of multiline values or ignored.
|
lines 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). Comments may appear on their own in
|
characters (`#' and `;' by default). Comments may appear on their own
|
||||||
an otherwise empty line or may be entered in lines holding values or
|
in an otherwise empty line or may be entered in lines holding values or
|
||||||
section names.
|
section names.
|
||||||
"""
|
"""
|
||||||
|
elements_added = set()
|
||||||
cursect = None # None, or a dictionary
|
cursect = None # None, or a dictionary
|
||||||
|
sectname = None
|
||||||
optname = None
|
optname = None
|
||||||
lineno = 0
|
lineno = 0
|
||||||
indent_level = 0
|
indent_level = 0
|
||||||
|
@ -598,13 +731,18 @@ class RawConfigParser:
|
||||||
if mo:
|
if mo:
|
||||||
sectname = mo.group('header')
|
sectname = mo.group('header')
|
||||||
if sectname in self._sections:
|
if sectname in self._sections:
|
||||||
|
if self._strict and sectname in elements_added:
|
||||||
|
raise DuplicateSectionError(sectname, fpname,
|
||||||
|
lineno)
|
||||||
cursect = self._sections[sectname]
|
cursect = self._sections[sectname]
|
||||||
|
elements_added.add(sectname)
|
||||||
elif sectname == DEFAULTSECT:
|
elif sectname == DEFAULTSECT:
|
||||||
cursect = self._defaults
|
cursect = self._defaults
|
||||||
else:
|
else:
|
||||||
cursect = self._dict()
|
cursect = self._dict()
|
||||||
cursect['__name__'] = sectname
|
cursect['__name__'] = sectname
|
||||||
self._sections[sectname] = cursect
|
self._sections[sectname] = cursect
|
||||||
|
elements_added.add(sectname)
|
||||||
# So sections can't start with a continuation line
|
# So sections can't start with a continuation line
|
||||||
optname = None
|
optname = None
|
||||||
# no section header in the file?
|
# no section header in the file?
|
||||||
|
@ -618,6 +756,11 @@ class RawConfigParser:
|
||||||
if not optname:
|
if not optname:
|
||||||
e = self._handle_error(e, fpname, lineno, line)
|
e = self._handle_error(e, fpname, lineno, line)
|
||||||
optname = self.optionxform(optname.rstrip())
|
optname = self.optionxform(optname.rstrip())
|
||||||
|
if (self._strict and
|
||||||
|
(sectname, optname) in elements_added):
|
||||||
|
raise DuplicateOptionError(sectname, optname,
|
||||||
|
fpname, lineno)
|
||||||
|
elements_added.add((sectname, optname))
|
||||||
# This check is fine because the OPTCRE cannot
|
# This check is fine because the OPTCRE cannot
|
||||||
# match if it would set optval to None
|
# match if it would set optval to None
|
||||||
if optval is not None:
|
if optval is not None:
|
||||||
|
@ -692,8 +835,7 @@ class ConfigParser(RawConfigParser):
|
||||||
return self._interpolate(section, option, value, d)
|
return self._interpolate(section, option, value, d)
|
||||||
|
|
||||||
def items(self, section, raw=False, vars=None):
|
def items(self, section, raw=False, vars=None):
|
||||||
"""Return a list of tuples with (name, value) for each option
|
"""Return a list of (name, value) tuples for each option in a section.
|
||||||
in the section.
|
|
||||||
|
|
||||||
All % interpolations are expanded in the return values, based on the
|
All % interpolations are expanded in the return values, based on the
|
||||||
defaults passed into the constructor, unless the optional argument
|
defaults passed into the constructor, unless the optional argument
|
||||||
|
@ -799,7 +941,8 @@ class SafeConfigParser(ConfigParser):
|
||||||
else:
|
else:
|
||||||
raise InterpolationSyntaxError(
|
raise InterpolationSyntaxError(
|
||||||
option, section,
|
option, section,
|
||||||
"'%%' must be followed by '%%' or '(', found: %r" % (rest,))
|
"'%%' 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. Extend ConfigParser.set: check for string values."""
|
||||||
|
@ -811,13 +954,11 @@ class SafeConfigParser(ConfigParser):
|
||||||
if self._optcre is self.OPTCRE or value:
|
if self._optcre is self.OPTCRE or value:
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
raise TypeError("option values must be strings")
|
raise TypeError("option values must be strings")
|
||||||
# check for bad percent signs:
|
# check for bad percent signs
|
||||||
# first, replace all "good" interpolations
|
if value:
|
||||||
tmp_value = value.replace('%%', '')
|
tmp_value = value.replace('%%', '') # escaped percent signs
|
||||||
tmp_value = self._interpvar_re.sub('', tmp_value)
|
tmp_value = self._interpvar_re.sub('', tmp_value) # valid syntax
|
||||||
# then, check if there's a lone percent sign left
|
if '%' in tmp_value:
|
||||||
percent_index = tmp_value.find('%')
|
raise ValueError("invalid interpolation syntax in %r at "
|
||||||
if percent_index != -1:
|
"position %d" % (value, tmp_value.find('%')))
|
||||||
raise ValueError("invalid interpolation syntax in %r at "
|
|
||||||
"position %d" % (value, percent_index))
|
|
||||||
ConfigParser.set(self, section, option, value)
|
ConfigParser.set(self, section, option, value)
|
||||||
|
|
|
@ -30,62 +30,28 @@ class CfgParserTestCaseClass(unittest.TestCase):
|
||||||
comment_prefixes = (';', '#')
|
comment_prefixes = (';', '#')
|
||||||
empty_lines_in_values = True
|
empty_lines_in_values = True
|
||||||
dict_type = configparser._default_dict
|
dict_type = configparser._default_dict
|
||||||
|
strict = False
|
||||||
|
|
||||||
def newconfig(self, defaults=None):
|
def newconfig(self, defaults=None):
|
||||||
arguments = dict(
|
arguments = dict(
|
||||||
|
defaults=defaults,
|
||||||
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,
|
||||||
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,
|
||||||
)
|
)
|
||||||
if defaults is None:
|
return self.config_class(**arguments)
|
||||||
self.cf = self.config_class(**arguments)
|
|
||||||
else:
|
|
||||||
self.cf = self.config_class(defaults,
|
|
||||||
**arguments)
|
|
||||||
return self.cf
|
|
||||||
|
|
||||||
def fromstring(self, string, defaults=None):
|
def fromstring(self, string, defaults=None):
|
||||||
cf = self.newconfig(defaults)
|
cf = self.newconfig(defaults)
|
||||||
sio = io.StringIO(string)
|
cf.read_string(string)
|
||||||
cf.readfp(sio)
|
|
||||||
return cf
|
return cf
|
||||||
|
|
||||||
class BasicTestCase(CfgParserTestCaseClass):
|
class BasicTestCase(CfgParserTestCaseClass):
|
||||||
|
|
||||||
def test_basic(self):
|
def basic_test(self, cf):
|
||||||
config_string = """\
|
|
||||||
[Foo Bar]
|
|
||||||
foo{0[0]}bar
|
|
||||||
[Spacey Bar]
|
|
||||||
foo {0[0]} bar
|
|
||||||
[Spacey Bar From The Beginning]
|
|
||||||
foo {0[0]} bar
|
|
||||||
baz {0[0]} qwe
|
|
||||||
[Commented Bar]
|
|
||||||
foo{0[1]} bar {1[1]} comment
|
|
||||||
baz{0[0]}qwe {1[0]}another one
|
|
||||||
[Long Line]
|
|
||||||
foo{0[1]} this line is much, much longer than my editor
|
|
||||||
likes it.
|
|
||||||
[Section\\with$weird%characters[\t]
|
|
||||||
[Internationalized Stuff]
|
|
||||||
foo[bg]{0[1]} Bulgarian
|
|
||||||
foo{0[0]}Default
|
|
||||||
foo[en]{0[0]}English
|
|
||||||
foo[de]{0[0]}Deutsch
|
|
||||||
[Spaces]
|
|
||||||
key with spaces {0[1]} value
|
|
||||||
another with spaces {0[0]} splat!
|
|
||||||
""".format(self.delimiters, self.comment_prefixes)
|
|
||||||
if self.allow_no_value:
|
|
||||||
config_string += (
|
|
||||||
"[NoValue]\n"
|
|
||||||
"option-without-value\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
cf = self.fromstring(config_string)
|
|
||||||
L = cf.sections()
|
L = cf.sections()
|
||||||
L.sort()
|
L.sort()
|
||||||
E = ['Commented Bar',
|
E = ['Commented Bar',
|
||||||
|
@ -137,6 +103,125 @@ another with spaces {0[0]} splat!
|
||||||
eq(cf.get('Long Line', 'foo'),
|
eq(cf.get('Long Line', 'foo'),
|
||||||
'this line is much, much longer than my editor\nlikes it.')
|
'this line is much, much longer than my editor\nlikes it.')
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
config_string = """\
|
||||||
|
[Foo Bar]
|
||||||
|
foo{0[0]}bar
|
||||||
|
[Spacey Bar]
|
||||||
|
foo {0[0]} bar
|
||||||
|
[Spacey Bar From The Beginning]
|
||||||
|
foo {0[0]} bar
|
||||||
|
baz {0[0]} qwe
|
||||||
|
[Commented Bar]
|
||||||
|
foo{0[1]} bar {1[1]} comment
|
||||||
|
baz{0[0]}qwe {1[0]}another one
|
||||||
|
[Long Line]
|
||||||
|
foo{0[1]} this line is much, much longer than my editor
|
||||||
|
likes it.
|
||||||
|
[Section\\with$weird%characters[\t]
|
||||||
|
[Internationalized Stuff]
|
||||||
|
foo[bg]{0[1]} Bulgarian
|
||||||
|
foo{0[0]}Default
|
||||||
|
foo[en]{0[0]}English
|
||||||
|
foo[de]{0[0]}Deutsch
|
||||||
|
[Spaces]
|
||||||
|
key with spaces {0[1]} value
|
||||||
|
another with spaces {0[0]} splat!
|
||||||
|
""".format(self.delimiters, self.comment_prefixes)
|
||||||
|
if self.allow_no_value:
|
||||||
|
config_string += (
|
||||||
|
"[NoValue]\n"
|
||||||
|
"option-without-value\n"
|
||||||
|
)
|
||||||
|
cf = self.fromstring(config_string)
|
||||||
|
self.basic_test(cf)
|
||||||
|
if self.strict:
|
||||||
|
with self.assertRaises(configparser.DuplicateOptionError):
|
||||||
|
cf.read_string(textwrap.dedent("""\
|
||||||
|
[Duplicate Options Here]
|
||||||
|
option {0[0]} with a value
|
||||||
|
option {0[1]} with another value
|
||||||
|
""".format(self.delimiters)))
|
||||||
|
with self.assertRaises(configparser.DuplicateSectionError):
|
||||||
|
cf.read_string(textwrap.dedent("""\
|
||||||
|
[And Now For Something]
|
||||||
|
completely different {0[0]} True
|
||||||
|
[And Now For Something]
|
||||||
|
the larch {0[1]} 1
|
||||||
|
""".format(self.delimiters)))
|
||||||
|
else:
|
||||||
|
cf.read_string(textwrap.dedent("""\
|
||||||
|
[Duplicate Options Here]
|
||||||
|
option {0[0]} with a value
|
||||||
|
option {0[1]} with another value
|
||||||
|
""".format(self.delimiters)))
|
||||||
|
|
||||||
|
cf.read_string(textwrap.dedent("""\
|
||||||
|
[And Now For Something]
|
||||||
|
completely different {0[0]} True
|
||||||
|
[And Now For Something]
|
||||||
|
the larch {0[1]} 1
|
||||||
|
""".format(self.delimiters)))
|
||||||
|
|
||||||
|
def test_basic_from_dict(self):
|
||||||
|
config = {
|
||||||
|
"Foo Bar": {
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
"Spacey Bar": {
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
"Spacey Bar From The Beginning": {
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qwe",
|
||||||
|
},
|
||||||
|
"Commented Bar": {
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "qwe",
|
||||||
|
},
|
||||||
|
"Long Line": {
|
||||||
|
"foo": "this line is much, much longer than my editor\nlikes "
|
||||||
|
"it.",
|
||||||
|
},
|
||||||
|
"Section\\with$weird%characters[\t": {
|
||||||
|
},
|
||||||
|
"Internationalized Stuff": {
|
||||||
|
"foo[bg]": "Bulgarian",
|
||||||
|
"foo": "Default",
|
||||||
|
"foo[en]": "English",
|
||||||
|
"foo[de]": "Deutsch",
|
||||||
|
},
|
||||||
|
"Spaces": {
|
||||||
|
"key with spaces": "value",
|
||||||
|
"another with spaces": "splat!",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.allow_no_value:
|
||||||
|
config.update({
|
||||||
|
"NoValue": {
|
||||||
|
"option-without-value": None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cf = self.newconfig()
|
||||||
|
cf.read_dict(config)
|
||||||
|
self.basic_test(cf)
|
||||||
|
if self.strict:
|
||||||
|
with self.assertRaises(configparser.DuplicateOptionError):
|
||||||
|
cf.read_dict({
|
||||||
|
"Duplicate Options Here": {
|
||||||
|
'option': 'with a value',
|
||||||
|
'OPTION': 'with another value',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
cf.read_dict({
|
||||||
|
"Duplicate Options Here": {
|
||||||
|
'option': 'with a value',
|
||||||
|
'OPTION': 'with another value',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def test_case_sensitivity(self):
|
def test_case_sensitivity(self):
|
||||||
cf = self.newconfig()
|
cf = self.newconfig()
|
||||||
cf.add_section("A")
|
cf.add_section("A")
|
||||||
|
@ -185,25 +270,25 @@ another with spaces {0[0]} splat!
|
||||||
"could not locate option, expecting case-insensitive defaults")
|
"could not locate option, expecting case-insensitive defaults")
|
||||||
|
|
||||||
def test_parse_errors(self):
|
def test_parse_errors(self):
|
||||||
self.newconfig()
|
cf = self.newconfig()
|
||||||
self.parse_error(configparser.ParsingError,
|
self.parse_error(cf, configparser.ParsingError,
|
||||||
"[Foo]\n"
|
"[Foo]\n"
|
||||||
"{}val-without-opt-name\n".format(self.delimiters[0]))
|
"{}val-without-opt-name\n".format(self.delimiters[0]))
|
||||||
self.parse_error(configparser.ParsingError,
|
self.parse_error(cf, configparser.ParsingError,
|
||||||
"[Foo]\n"
|
"[Foo]\n"
|
||||||
"{}val-without-opt-name\n".format(self.delimiters[1]))
|
"{}val-without-opt-name\n".format(self.delimiters[1]))
|
||||||
e = self.parse_error(configparser.MissingSectionHeaderError,
|
e = self.parse_error(cf, configparser.MissingSectionHeaderError,
|
||||||
"No Section!\n")
|
"No Section!\n")
|
||||||
self.assertEqual(e.args, ('<???>', 1, "No Section!\n"))
|
self.assertEqual(e.args, ('<???>', 1, "No Section!\n"))
|
||||||
if not self.allow_no_value:
|
if not self.allow_no_value:
|
||||||
e = self.parse_error(configparser.ParsingError,
|
e = self.parse_error(cf, configparser.ParsingError,
|
||||||
"[Foo]\n wrong-indent\n")
|
"[Foo]\n wrong-indent\n")
|
||||||
self.assertEqual(e.args, ('<???>',))
|
self.assertEqual(e.args, ('<???>',))
|
||||||
|
|
||||||
def parse_error(self, exc, src):
|
def parse_error(self, cf, exc, src):
|
||||||
sio = io.StringIO(src)
|
sio = io.StringIO(src)
|
||||||
with self.assertRaises(exc) as cm:
|
with self.assertRaises(exc) as cm:
|
||||||
self.cf.readfp(sio)
|
cf.read_file(sio)
|
||||||
return cm.exception
|
return cm.exception
|
||||||
|
|
||||||
def test_query_errors(self):
|
def test_query_errors(self):
|
||||||
|
@ -217,15 +302,15 @@ another with spaces {0[0]} splat!
|
||||||
cf.options("Foo")
|
cf.options("Foo")
|
||||||
with self.assertRaises(configparser.NoSectionError):
|
with self.assertRaises(configparser.NoSectionError):
|
||||||
cf.set("foo", "bar", "value")
|
cf.set("foo", "bar", "value")
|
||||||
e = self.get_error(configparser.NoSectionError, "foo", "bar")
|
e = self.get_error(cf, configparser.NoSectionError, "foo", "bar")
|
||||||
self.assertEqual(e.args, ("foo",))
|
self.assertEqual(e.args, ("foo",))
|
||||||
cf.add_section("foo")
|
cf.add_section("foo")
|
||||||
e = self.get_error(configparser.NoOptionError, "foo", "bar")
|
e = self.get_error(cf, configparser.NoOptionError, "foo", "bar")
|
||||||
self.assertEqual(e.args, ("bar", "foo"))
|
self.assertEqual(e.args, ("bar", "foo"))
|
||||||
|
|
||||||
def get_error(self, exc, section, option):
|
def get_error(self, cf, exc, section, option):
|
||||||
try:
|
try:
|
||||||
self.cf.get(section, option)
|
cf.get(section, option)
|
||||||
except exc as e:
|
except exc as e:
|
||||||
return e
|
return e
|
||||||
else:
|
else:
|
||||||
|
@ -262,7 +347,31 @@ another with spaces {0[0]} splat!
|
||||||
cf.add_section("Foo")
|
cf.add_section("Foo")
|
||||||
with self.assertRaises(configparser.DuplicateSectionError) as cm:
|
with self.assertRaises(configparser.DuplicateSectionError) as cm:
|
||||||
cf.add_section("Foo")
|
cf.add_section("Foo")
|
||||||
self.assertEqual(cm.exception.args, ("Foo",))
|
e = cm.exception
|
||||||
|
self.assertEqual(str(e), "Section 'Foo' already exists")
|
||||||
|
self.assertEqual(e.args, ("Foo", None, None))
|
||||||
|
|
||||||
|
if self.strict:
|
||||||
|
with self.assertRaises(configparser.DuplicateSectionError) as cm:
|
||||||
|
cf.read_string(textwrap.dedent("""\
|
||||||
|
[Foo]
|
||||||
|
will this be added{equals}True
|
||||||
|
[Bar]
|
||||||
|
what about this{equals}True
|
||||||
|
[Foo]
|
||||||
|
oops{equals}this won't
|
||||||
|
""".format(equals=self.delimiters[0])), source='<foo-bar>')
|
||||||
|
e = cm.exception
|
||||||
|
self.assertEqual(str(e), "While reading from <foo-bar> [line 5]: "
|
||||||
|
"section 'Foo' already exists")
|
||||||
|
self.assertEqual(e.args, ("Foo", '<foo-bar>', 5))
|
||||||
|
|
||||||
|
with self.assertRaises(configparser.DuplicateOptionError) as cm:
|
||||||
|
cf.read_dict({'Bar': {'opt': 'val', 'OPT': 'is really `opt`'}})
|
||||||
|
e = cm.exception
|
||||||
|
self.assertEqual(str(e), "While reading from <dict>: option 'opt' "
|
||||||
|
"in section 'Bar' already exists")
|
||||||
|
self.assertEqual(e.args, ("Bar", "opt", "<dict>", None))
|
||||||
|
|
||||||
def test_write(self):
|
def test_write(self):
|
||||||
config_string = (
|
config_string = (
|
||||||
|
@ -392,6 +501,11 @@ another with spaces {0[0]} splat!
|
||||||
self.assertEqual(L, expected)
|
self.assertEqual(L, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class StrictTestCase(BasicTestCase):
|
||||||
|
config_class = configparser.RawConfigParser
|
||||||
|
strict = True
|
||||||
|
|
||||||
|
|
||||||
class ConfigParserTestCase(BasicTestCase):
|
class ConfigParserTestCase(BasicTestCase):
|
||||||
config_class = configparser.ConfigParser
|
config_class = configparser.ConfigParser
|
||||||
|
|
||||||
|
@ -409,7 +523,7 @@ class ConfigParserTestCase(BasicTestCase):
|
||||||
"something with lots of interpolation (9 steps)")
|
"something with lots of interpolation (9 steps)")
|
||||||
eq(cf.get("Foo", "bar10"),
|
eq(cf.get("Foo", "bar10"),
|
||||||
"something with lots of interpolation (10 steps)")
|
"something with lots of interpolation (10 steps)")
|
||||||
e = self.get_error(configparser.InterpolationDepthError, "Foo", "bar11")
|
e = self.get_error(cf, configparser.InterpolationDepthError, "Foo", "bar11")
|
||||||
self.assertEqual(e.args, ("bar11", "Foo", rawval[self.config_class]))
|
self.assertEqual(e.args, ("bar11", "Foo", rawval[self.config_class]))
|
||||||
|
|
||||||
def test_interpolation_missing_value(self):
|
def test_interpolation_missing_value(self):
|
||||||
|
@ -417,8 +531,8 @@ class ConfigParserTestCase(BasicTestCase):
|
||||||
configparser.ConfigParser: '%(reference)s',
|
configparser.ConfigParser: '%(reference)s',
|
||||||
configparser.SafeConfigParser: '',
|
configparser.SafeConfigParser: '',
|
||||||
}
|
}
|
||||||
self.get_interpolation_config()
|
cf = self.get_interpolation_config()
|
||||||
e = self.get_error(configparser.InterpolationMissingOptionError,
|
e = self.get_error(cf, configparser.InterpolationMissingOptionError,
|
||||||
"Interpolation Error", "name")
|
"Interpolation Error", "name")
|
||||||
self.assertEqual(e.reference, "reference")
|
self.assertEqual(e.reference, "reference")
|
||||||
self.assertEqual(e.section, "Interpolation Error")
|
self.assertEqual(e.section, "Interpolation Error")
|
||||||
|
@ -482,7 +596,7 @@ class MultilineValuesTestCase(BasicTestCase):
|
||||||
# during performance updates in Python 3.2
|
# during performance updates in Python 3.2
|
||||||
cf_from_file = self.newconfig()
|
cf_from_file = self.newconfig()
|
||||||
with open(support.TESTFN) as f:
|
with open(support.TESTFN) as f:
|
||||||
cf_from_file.readfp(f)
|
cf_from_file.read_file(f)
|
||||||
self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'),
|
self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'),
|
||||||
self.wonderful_spam.replace('\t\n', '\n'))
|
self.wonderful_spam.replace('\t\n', '\n'))
|
||||||
|
|
||||||
|
@ -645,15 +759,15 @@ class SortedTestCase(RawConfigParserTestCase):
|
||||||
dict_type = SortedDict
|
dict_type = SortedDict
|
||||||
|
|
||||||
def test_sorted(self):
|
def test_sorted(self):
|
||||||
self.fromstring("[b]\n"
|
cf = self.fromstring("[b]\n"
|
||||||
"o4=1\n"
|
"o4=1\n"
|
||||||
"o3=2\n"
|
"o3=2\n"
|
||||||
"o2=3\n"
|
"o2=3\n"
|
||||||
"o1=4\n"
|
"o1=4\n"
|
||||||
"[a]\n"
|
"[a]\n"
|
||||||
"k=v\n")
|
"k=v\n")
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
self.cf.write(output)
|
cf.write(output)
|
||||||
self.assertEquals(output.getvalue(),
|
self.assertEquals(output.getvalue(),
|
||||||
"[a]\n"
|
"[a]\n"
|
||||||
"k = v\n\n"
|
"k = v\n\n"
|
||||||
|
@ -697,6 +811,7 @@ def test_main():
|
||||||
SafeConfigParserTestCaseNoValue,
|
SafeConfigParserTestCaseNoValue,
|
||||||
SafeConfigParserTestCaseTrickyFile,
|
SafeConfigParserTestCaseTrickyFile,
|
||||||
SortedTestCase,
|
SortedTestCase,
|
||||||
|
StrictTestCase,
|
||||||
CompatibleTestCase,
|
CompatibleTestCase,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,9 @@ Extensions
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #9452: Add read_file, read_string, and read_dict to the configparser
|
||||||
|
API; new source attribute to exceptions.
|
||||||
|
|
||||||
- Issue #6231: Fix xml.etree.ElementInclude to include the tail of the
|
- Issue #6231: Fix xml.etree.ElementInclude to include the tail of the
|
||||||
current node.
|
current node.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue