mirror of
https://github.com/python/cpython.git
synced 2025-08-24 10:45:53 +00:00
gh-107625: configparser: Raise error if a missing value is continued (GH-107651)
Co-authored-by: Éric <merwok@netwok.org> Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
This commit is contained in:
parent
27858e2a17
commit
e800265aa1
4 changed files with 55 additions and 0 deletions
|
@ -978,6 +978,10 @@ ConfigParser Objects
|
||||||
The default *dict_type* is :class:`dict`, since it now preserves
|
The default *dict_type* is :class:`dict`, since it now preserves
|
||||||
insertion order.
|
insertion order.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
Raise a :exc:`MultilineContinuationError` when *allow_no_value* is
|
||||||
|
``True``, and a key without a value is continued with an indented line.
|
||||||
|
|
||||||
.. method:: defaults()
|
.. method:: defaults()
|
||||||
|
|
||||||
Return a dictionary containing the instance-wide defaults.
|
Return a dictionary containing the instance-wide defaults.
|
||||||
|
@ -1349,6 +1353,13 @@ Exceptions
|
||||||
The ``filename`` attribute and :meth:`!__init__` constructor argument were
|
The ``filename`` attribute and :meth:`!__init__` constructor argument were
|
||||||
removed. They have been available using the name ``source`` since 3.2.
|
removed. They have been available using the name ``source`` since 3.2.
|
||||||
|
|
||||||
|
.. exception:: MultilineContinuationError
|
||||||
|
|
||||||
|
Exception raised when a key without a corresponding value is continued with
|
||||||
|
an indented line.
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
||||||
.. rubric:: Footnotes
|
.. rubric:: Footnotes
|
||||||
|
|
||||||
.. [1] Config parsers allow for heavy customization. If you are interested in
|
.. [1] Config parsers allow for heavy customization. If you are interested in
|
||||||
|
|
|
@ -152,6 +152,7 @@ __all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
|
||||||
"NoOptionError", "InterpolationError", "InterpolationDepthError",
|
"NoOptionError", "InterpolationError", "InterpolationDepthError",
|
||||||
"InterpolationMissingOptionError", "InterpolationSyntaxError",
|
"InterpolationMissingOptionError", "InterpolationSyntaxError",
|
||||||
"ParsingError", "MissingSectionHeaderError",
|
"ParsingError", "MissingSectionHeaderError",
|
||||||
|
"MultilineContinuationError",
|
||||||
"ConfigParser", "RawConfigParser",
|
"ConfigParser", "RawConfigParser",
|
||||||
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
|
"Interpolation", "BasicInterpolation", "ExtendedInterpolation",
|
||||||
"SectionProxy", "ConverterMapping",
|
"SectionProxy", "ConverterMapping",
|
||||||
|
@ -322,6 +323,19 @@ class MissingSectionHeaderError(ParsingError):
|
||||||
self.args = (filename, lineno, line)
|
self.args = (filename, lineno, line)
|
||||||
|
|
||||||
|
|
||||||
|
class MultilineContinuationError(ParsingError):
|
||||||
|
"""Raised when a key without value is followed by continuation line"""
|
||||||
|
def __init__(self, filename, lineno, line):
|
||||||
|
Error.__init__(
|
||||||
|
self,
|
||||||
|
"Key without value continued with an indented line.\n"
|
||||||
|
"file: %r, line: %d\n%r"
|
||||||
|
%(filename, lineno, line))
|
||||||
|
self.source = filename
|
||||||
|
self.lineno = lineno
|
||||||
|
self.line = line
|
||||||
|
self.args = (filename, lineno, line)
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -987,6 +1001,8 @@ class RawConfigParser(MutableMapping):
|
||||||
cur_indent_level = first_nonspace.start() if first_nonspace else 0
|
cur_indent_level = first_nonspace.start() if first_nonspace else 0
|
||||||
if (cursect is not None and optname and
|
if (cursect is not None and optname and
|
||||||
cur_indent_level > indent_level):
|
cur_indent_level > indent_level):
|
||||||
|
if cursect[optname] is None:
|
||||||
|
raise MultilineContinuationError(fpname, lineno, line)
|
||||||
cursect[optname].append(value)
|
cursect[optname].append(value)
|
||||||
# a section header or option header?
|
# a section header or option header?
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1555,6 +1555,30 @@ class ReadFileTestCase(unittest.TestCase):
|
||||||
"'[badbad'"
|
"'[badbad'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_keys_without_value_with_extra_whitespace(self):
|
||||||
|
lines = [
|
||||||
|
'[SECT]\n',
|
||||||
|
'KEY1\n',
|
||||||
|
' KEY2 = VAL2\n', # note the Space before the key!
|
||||||
|
]
|
||||||
|
parser = configparser.ConfigParser(
|
||||||
|
comment_prefixes="",
|
||||||
|
allow_no_value=True,
|
||||||
|
strict=False,
|
||||||
|
delimiters=('=',),
|
||||||
|
interpolation=None,
|
||||||
|
)
|
||||||
|
with self.assertRaises(configparser.MultilineContinuationError) as dse:
|
||||||
|
parser.read_file(lines)
|
||||||
|
self.assertEqual(
|
||||||
|
str(dse.exception),
|
||||||
|
"Key without value continued with an indented line.\n"
|
||||||
|
"file: '<???>', line: 3\n"
|
||||||
|
"' KEY2 = VAL2\\n'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CoverageOneHundredTestCase(unittest.TestCase):
|
class CoverageOneHundredTestCase(unittest.TestCase):
|
||||||
"""Covers edge cases in the codebase."""
|
"""Covers edge cases in the codebase."""
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Raise :exc:`configparser.ParsingError` from :meth:`~configparser.ConfigParser.read`
|
||||||
|
and :meth:`~configparser.ConfigParser.read_file` methods of
|
||||||
|
:class:`configparser.ConfigParser` if a key without a corresponding value
|
||||||
|
is continued (that is, followed by an indented line).
|
Loading…
Add table
Add a link
Reference in a new issue