Skip to content

Commit 59093b5

Browse files
committed
gh-157466: Fix configparser writing a trailing space for empty values
RawConfigParser._write_section() always concatenates the padded delimiter, so an option with an empty value is written as "key = ", leaving a space at the end of the line. Drop that space when the value is empty, but keep it when the delimiter itself ends in whitespace (delimiters=(' ',)), where it is the separator and is needed to read the option back. Values that genuinely end in whitespace are untouched. write() is the only caller of _write_section(), and all three call sites pass the same delimiter, so the guard belongs there.
1 parent fd0970c commit 59093b5

3 files changed

Lines changed: 33 additions & 0 deletions

File tree

Lib/configparser.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,13 @@ def _write_section(self, fp, section_name, section_items, delimiter, unnamed=Fal
997997
# Convert all possible line-endings into '\n\t'
998998
value = (delimiter + str(value).replace('\r\n', '\n')
999999
.replace('\r', '\n').replace('\n', '\n\t'))
1000+
if value == delimiter and not self._delimiters[0][-1:].isspace():
1001+
# The value is empty, so the space that
1002+
# `space_around_delimiters` appends would be left
1003+
# dangling at the end of the line. Keep it only when
1004+
# the delimiter itself ends in whitespace, where it is
1005+
# needed to read the option back.
1006+
value = value.rstrip(' ')
10001007
else:
10011008
value = ""
10021009
fp.write("{}{}\n".format(key, value))

Lib/test/test_configparser.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,27 @@ def test_write(self):
752752
)
753753
self.assertEqual(output.getvalue(), expect_string)
754754

755+
def test_write_empty_value(self):
756+
# gh-157466: an empty value must not leave the space that
757+
# `space_around_delimiters` appends dangling at end of line,
758+
# while a value that really ends in whitespace keeps it.
759+
cf = self.newconfig()
760+
cf.add_section('sect')
761+
cf.set('sect', 'empty', '')
762+
cf.set('sect', 'padded', 'value ')
763+
for space_around_delimiters in (True, False):
764+
delimiter = self.delimiters[0]
765+
if space_around_delimiters:
766+
delimiter = " {} ".format(delimiter)
767+
output = io.StringIO()
768+
cf.write(output, space_around_delimiters=space_around_delimiters)
769+
self.assertEqual(
770+
output.getvalue(),
771+
"[sect]\n"
772+
"empty{}\n"
773+
"padded{}value \n"
774+
"\n".format(delimiter.rstrip(' '), delimiter))
775+
755776
def test_set_string_types(self):
756777
cf = self.fromstring("[sect]\n"
757778
"option1{eq}foo\n".format(eq=self.delimiters[0]))
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix :mod:`configparser` leaving a trailing space at the end of the line when
2+
:meth:`~configparser.RawConfigParser.write` writes an option with an empty
3+
value and ``space_around_delimiters`` is true. The space is still written
4+
when the delimiter itself ends in whitespace, where it is needed to read the
5+
option back.

0 commit comments

Comments
 (0)