From f720c66a7579b7be063be5cb57ab704684aadc51 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Thu, 25 Jun 2026 01:36:36 +0200 Subject: [PATCH 1/3] Fix StopIteration in restore_yaml_comments when default_data ends with comment or blank line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `restore_yaml_comments` used a bare `next()` call inside an inner `while True` loop to scan past comment/blank lines in `default_data`. If the file ended on a comment or blank line (very common — editors often append a trailing newline after the last comment), `next()` raised `StopIteration`, which propagated out of the function and crashed any caller, including `Configuration.dump()`. Fix: catch `StopIteration`, set `line = None`, and break out of both the inner and outer loops so the orphaned comment is silently discarded rather than crashing the caller. Fixes #148. --- confuse/yaml_util.py | 10 +++++++++- test/test_yaml.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/confuse/yaml_util.py b/confuse/yaml_util.py index ea124cb..dd23132 100644 --- a/confuse/yaml_util.py +++ b/confuse/yaml_util.py @@ -227,10 +227,18 @@ def restore_yaml_comments(data: str, default_data: str) -> str: else: continue while True: - line = next(default_lines) + try: + line = next(default_lines) + except StopIteration: + # File ends with a comment or blank line and no following key; + # discard the accumulated comment and stop scanning. + line = None + break if line and not line.startswith("#"): break comment += f"{line}\n" + if line is None: + break key = line.split(":")[0].strip() comment_map[key] = comment out_lines = iter(data.splitlines()) diff --git a/test/test_yaml.py b/test/test_yaml.py index 7b636ac..ce90360 100644 --- a/test/test_yaml.py +++ b/test/test_yaml.py @@ -5,6 +5,7 @@ import yaml import confuse +from confuse.yaml_util import restore_yaml_comments from . import TempDir @@ -114,3 +115,30 @@ def test_list_unchanged(self): def test_invalid_yaml_string_unchanged(self): v = confuse.yaml_util.parse_as_scalar("!", confuse.Loader) assert v == "!" + + +class RestoreYamlCommentsTest(unittest.TestCase): + def test_trailing_comment_does_not_raise(self): + """restore_yaml_comments must not raise StopIteration when + default_data ends with a comment line (issue #148).""" + default_data = "key1: value1\n# trailing comment\n" + data = "key1: value1\n" + # Must complete without raising StopIteration + result = restore_yaml_comments(data, default_data) + assert "key1: value1" in result + + def test_trailing_blank_line_does_not_raise(self): + """restore_yaml_comments must not raise StopIteration when + default_data ends with a blank line (issue #148).""" + default_data = "key1: value1\n\n" + data = "key1: value1\n" + result = restore_yaml_comments(data, default_data) + assert "key1: value1" in result + + def test_comments_still_restored_before_key(self): + """Comments before a key in default_data are prepended to that key + in the output.""" + default_data = "# Comment for key1\nkey1: value1\n" + data = "key1: value1\n" + result = restore_yaml_comments(data, default_data) + assert result == "# Comment for key1\nkey1: value1\n" From 007228186610e3855d9b3fb34fab773360a05f83 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Fri, 26 Jun 2026 12:07:33 +0200 Subject: [PATCH 2/3] Document restore_yaml_comments fix --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index d1fb317..cac1476 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,8 @@ Unreleased - Require `typing_extensions` on older Python versions (and use `typing` on newer versions). [#189](https://github.com/beetbox/confuse/issues/189) +- Fix ``restore_yaml_comments`` crashing when default data ends with a comment + or blank line. [#148](https://github.com/beetbox/confuse/issues/148) v2.2.0 ------ From 88d2956abcc3d4b701644db9475ed0f90d50cdc2 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Sat, 27 Jun 2026 10:18:32 +0200 Subject: [PATCH 3/3] Fix restore_yaml_comments mypy type check --- confuse/yaml_util.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/confuse/yaml_util.py b/confuse/yaml_util.py index dd23132..d6e5ad8 100644 --- a/confuse/yaml_util.py +++ b/confuse/yaml_util.py @@ -226,18 +226,19 @@ def restore_yaml_comments(data: str, default_data: str) -> str: comment = f"{line}\n" else: continue + found_key = False while True: try: line = next(default_lines) except StopIteration: # File ends with a comment or blank line and no following key; # discard the accumulated comment and stop scanning. - line = None break if line and not line.startswith("#"): + found_key = True break comment += f"{line}\n" - if line is None: + if not found_key: break key = line.split(":")[0].strip() comment_map[key] = comment