From 3487cc12f6cc726f7d95aff19c3ff2eb9f98c82c Mon Sep 17 00:00:00 2001 From: Nathan McDougall Date: Fri, 25 Jul 2025 15:03:24 +1200 Subject: [PATCH 1/2] Support empty `.pre-commit-config.yaml files` --- src/usethis/_integrations/pre_commit/io_.py | 6 ++++-- .../pre_commit/test_pre_commit_io_.py | 19 ++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/usethis/_integrations/pre_commit/io_.py b/src/usethis/_integrations/pre_commit/io_.py index a5aec3bb..25c5110b 100644 --- a/src/usethis/_integrations/pre_commit/io_.py +++ b/src/usethis/_integrations/pre_commit/io_.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pydantic import ValidationError +from ruamel.yaml.comments import CommentedMap from usethis._config import usethis_config from usethis._console import tick_print @@ -15,8 +16,6 @@ if TYPE_CHECKING: from collections.abc import Generator - from ruamel.yaml.comments import CommentedMap - from usethis._integrations.file.yaml.io_ import YAMLLiteral @@ -62,6 +61,9 @@ def edit_pre_commit_config_yaml() -> Generator[PreCommitConfigYAMLDocument, None def _validate_config(ruamel_content: YAMLLiteral) -> JsonSchemaForPreCommitConfigYaml: + if isinstance(ruamel_content, CommentedMap) and not ruamel_content: + ruamel_content["repos"] = [] + try: return JsonSchemaForPreCommitConfigYaml.model_validate(ruamel_content) except ValidationError as err: diff --git a/tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py b/tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py index 60100b3a..be39a563 100644 --- a/tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py +++ b/tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py @@ -1,11 +1,6 @@ from pathlib import Path -import pytest - -from usethis._integrations.pre_commit.io_ import ( - PreCommitConfigYAMLConfigError, - edit_pre_commit_config_yaml, -) +from usethis._integrations.pre_commit.io_ import edit_pre_commit_config_yaml from usethis._test import change_cwd @@ -35,14 +30,16 @@ def test_unchanged(self, tmp_path: Path): # Assert assert (tmp_path / ".pre-commit-config.yaml").read_text() == content_str - def test_empty_is_invalid(self, tmp_path: Path): + def test_empty_is_valid(self, tmp_path: Path): # Arrange (tmp_path / ".pre-commit-config.yaml").write_text("") - # Act, Assert + # Act with ( change_cwd(tmp_path), - pytest.raises(PreCommitConfigYAMLConfigError), - edit_pre_commit_config_yaml(), + edit_pre_commit_config_yaml() as doc, ): - pass + doc.content["repos"] = [] + + # Assert + assert (tmp_path / ".pre-commit-config.yaml").read_text() == "repos: []\n" From 5dfd1cb57a26cbecd93f33d3176816b68fe0897e Mon Sep 17 00:00:00 2001 From: Nathan McDougall Date: Fri, 25 Jul 2025 15:23:16 +1200 Subject: [PATCH 2/2] Don't modify empty files if unchanged --- src/usethis/_integrations/file/yaml/io_.py | 5 +++++ src/usethis/_integrations/pre_commit/io_.py | 2 +- .../_integrations/pre_commit/test_pre_commit_io_.py | 13 ++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/usethis/_integrations/file/yaml/io_.py b/src/usethis/_integrations/file/yaml/io_.py index 3f38f676..055816e3 100644 --- a/src/usethis/_integrations/file/yaml/io_.py +++ b/src/usethis/_integrations/file/yaml/io_.py @@ -474,7 +474,12 @@ def edit_yaml( except YAMLError as err: msg = f"Error reading '{yaml_path}':\n{err}" raise YAMLDecodeError(msg) from None + + start_empty = not yaml_document.content yield yaml_document + if start_empty and not yaml_document.content: + # No change + return yaml_document.roundtripper.dump(yaml_document.content, stream=yaml_path) diff --git a/src/usethis/_integrations/pre_commit/io_.py b/src/usethis/_integrations/pre_commit/io_.py index 25c5110b..f8b11a87 100644 --- a/src/usethis/_integrations/pre_commit/io_.py +++ b/src/usethis/_integrations/pre_commit/io_.py @@ -62,7 +62,7 @@ def edit_pre_commit_config_yaml() -> Generator[PreCommitConfigYAMLDocument, None def _validate_config(ruamel_content: YAMLLiteral) -> JsonSchemaForPreCommitConfigYaml: if isinstance(ruamel_content, CommentedMap) and not ruamel_content: - ruamel_content["repos"] = [] + ruamel_content = CommentedMap({"repos": []}) try: return JsonSchemaForPreCommitConfigYaml.model_validate(ruamel_content) diff --git a/tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py b/tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py index be39a563..ab1850d6 100644 --- a/tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py +++ b/tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py @@ -30,7 +30,7 @@ def test_unchanged(self, tmp_path: Path): # Assert assert (tmp_path / ".pre-commit-config.yaml").read_text() == content_str - def test_empty_is_valid(self, tmp_path: Path): + def test_start_with_empty_file(self, tmp_path: Path): # Arrange (tmp_path / ".pre-commit-config.yaml").write_text("") @@ -43,3 +43,14 @@ def test_empty_is_valid(self, tmp_path: Path): # Assert assert (tmp_path / ".pre-commit-config.yaml").read_text() == "repos: []\n" + + def test_empty_valid_but_unchanged(self, tmp_path: Path): + # Arrange + (tmp_path / ".pre-commit-config.yaml").write_text("") + + # Act + with change_cwd(tmp_path), edit_pre_commit_config_yaml(): + pass + + # Assert + assert (tmp_path / ".pre-commit-config.yaml").read_text() == ""