Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/usethis/_integrations/ci/bitbucket/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from usethis._integrations.ci.bitbucket.dump import bitbucket_fancy_dump
from usethis._integrations.ci.bitbucket.io_ import (
edit_bitbucket_pipelines_yaml,
read_bitbucket_pipelines_yaml,
)
from usethis._integrations.ci.bitbucket.schema import Definitions
from usethis._integrations.file.yaml.update import update_ruamel_yaml_map
Expand All @@ -16,7 +17,7 @@


def get_cache_by_name() -> dict[str, Cache]:
with edit_bitbucket_pipelines_yaml() as doc:
with read_bitbucket_pipelines_yaml() as doc:
config = doc.model

if config.definitions is None:
Expand Down
16 changes: 15 additions & 1 deletion src/usethis/_integrations/ci/bitbucket/io_.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from usethis._config import usethis_config
from usethis._console import tick_print
from usethis._integrations.ci.bitbucket.schema import PipelinesConfiguration
from usethis._integrations.file.yaml.io_ import edit_yaml
from usethis._integrations.file.yaml.io_ import edit_yaml, read_yaml
from usethis.errors import FileConfigError

if TYPE_CHECKING:
Expand Down Expand Up @@ -66,6 +66,20 @@ def edit_bitbucket_pipelines_yaml() -> Generator[
_validate_config(doc.content)


@contextmanager
def read_bitbucket_pipelines_yaml() -> Generator[
BitbucketPipelinesYAMLDocument, None, None
]:
"""A context manager to read 'bitbucket-pipelines.yml'."""
name = "bitbucket-pipelines.yml"
path = usethis_config.cpd() / name

with read_yaml(path) as doc:
config = _validate_config(doc.content)
yield BitbucketPipelinesYAMLDocument(content=doc.content, model=config)
_validate_config(doc.content)


def _validate_config(ruamel_content: YAMLLiteral) -> PipelinesConfiguration:
try:
return PipelinesConfiguration.model_validate(ruamel_content)
Expand Down
7 changes: 5 additions & 2 deletions src/usethis/_integrations/ci/bitbucket/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
from usethis._integrations.ci.bitbucket.cache import _add_caches_via_doc, remove_cache
from usethis._integrations.ci.bitbucket.dump import bitbucket_fancy_dump
from usethis._integrations.ci.bitbucket.errors import UnexpectedImportPipelineError
from usethis._integrations.ci.bitbucket.io_ import edit_bitbucket_pipelines_yaml
from usethis._integrations.ci.bitbucket.io_ import (
edit_bitbucket_pipelines_yaml,
read_bitbucket_pipelines_yaml,
)
from usethis._integrations.ci.bitbucket.pipeweld import (
apply_pipeweld_instruction_via_doc,
get_pipeweld_pipeline_from_default,
Expand Down Expand Up @@ -403,7 +406,7 @@ def get_steps_in_default() -> list[Step]:
if not (usethis_config.cpd() / "bitbucket-pipelines.yml").exists():
return []

with edit_bitbucket_pipelines_yaml() as doc:
with read_bitbucket_pipelines_yaml() as doc:
config = doc.model

if config.pipelines is None:
Expand Down
21 changes: 16 additions & 5 deletions src/usethis/_integrations/file/yaml/io_.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,19 +468,30 @@ def edit_yaml(
guess_indent: bool = True,
) -> Generator[YAMLDocument, None, None]:
"""A context manager to modify a YAML file in-place, with managed read and write."""
with read_yaml(yaml_path, guess_indent=guess_indent) as yaml_document:
yield yaml_document
start_empty = not yaml_document.content
if start_empty and not yaml_document.content:
# No change
return
yaml_document.roundtripper.dump(yaml_document.content, stream=yaml_path)


@contextmanager
def read_yaml(
yaml_path: Path,
*,
guess_indent: bool = True,
) -> Generator[YAMLDocument, None, None]:
"""A context manager to read a YAML file."""
with yaml_path.open(mode="r") as f:
try:
yaml_document = _get_yaml_document(f, guess_indent=guess_indent)
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)


def _get_yaml_document(
Expand Down
7 changes: 5 additions & 2 deletions src/usethis/_integrations/pre_commit/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
from usethis._console import box_print, tick_print
from usethis._integrations.file.yaml.update import update_ruamel_yaml_map
from usethis._integrations.pre_commit.dump import pre_commit_fancy_dump
from usethis._integrations.pre_commit.io_ import edit_pre_commit_config_yaml
from usethis._integrations.pre_commit.io_ import (
edit_pre_commit_config_yaml,
read_pre_commit_config_yaml,
)
from usethis._integrations.pre_commit.schema import (
HookDefinition,
Language,
Expand Down Expand Up @@ -232,7 +235,7 @@ def get_hook_ids() -> list[str]:
if not path.exists():
return []

with edit_pre_commit_config_yaml() as doc:
with read_pre_commit_config_yaml() as doc:
return extract_hook_ids(doc.model)


Expand Down
14 changes: 13 additions & 1 deletion src/usethis/_integrations/pre_commit/io_.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from usethis._config import usethis_config
from usethis._console import tick_print
from usethis._integrations.file.yaml.io_ import edit_yaml
from usethis._integrations.file.yaml.io_ import edit_yaml, read_yaml
from usethis._integrations.pre_commit.schema import JsonSchemaForPreCommitConfigYaml
from usethis.errors import FileConfigError

Expand Down Expand Up @@ -60,6 +60,18 @@ def edit_pre_commit_config_yaml() -> Generator[PreCommitConfigYAMLDocument, None
_validate_config(doc.content)


@contextmanager
def read_pre_commit_config_yaml() -> Generator[PreCommitConfigYAMLDocument, None, None]:
"""A context manager to read '.pre-commit-config.yaml'."""
name = ".pre-commit-config.yaml"
path = usethis_config.cpd() / name

with read_yaml(path) as doc:
config = _validate_config(doc.content)
yield PreCommitConfigYAMLDocument(content=doc.content, model=config)
_validate_config(doc.content)


def _validate_config(ruamel_content: YAMLLiteral) -> JsonSchemaForPreCommitConfigYaml:
if isinstance(ruamel_content, CommentedMap) and not ruamel_content:
ruamel_content = CommentedMap({"repos": []})
Expand Down
22 changes: 22 additions & 0 deletions tests/usethis/_integrations/ci/bitbucket/test_bitbucket_io_.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from usethis._integrations.ci.bitbucket.io_ import (
BitbucketPipelinesYAMLConfigError,
edit_bitbucket_pipelines_yaml,
read_bitbucket_pipelines_yaml,
)
from usethis._test import change_cwd

Expand Down Expand Up @@ -147,3 +148,24 @@ def test_invalid_contents(self, tmp_path: Path):
edit_bitbucket_pipelines_yaml() as _,
):
pass


class TestReadBitbucketPipelinesYAML:
def test_quote_style_preserved(self, tmp_path: Path):
# Arrange
content_str = """\
pipelines:
default:
- step:
script:
- 'echo'
"""

(tmp_path / "bitbucket-pipelines.yml").write_text(content_str)

# Act
with change_cwd(tmp_path), read_bitbucket_pipelines_yaml():
pass

# Assert
assert (tmp_path / "bitbucket-pipelines.yml").read_text() == content_str
24 changes: 23 additions & 1 deletion tests/usethis/_integrations/pre_commit/test_pre_commit_io_.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from pathlib import Path

from usethis._integrations.pre_commit.io_ import edit_pre_commit_config_yaml
from usethis._integrations.pre_commit.io_ import (
edit_pre_commit_config_yaml,
read_pre_commit_config_yaml,
)
from usethis._test import change_cwd


Expand Down Expand Up @@ -54,3 +57,22 @@ def test_empty_valid_but_unchanged(self, tmp_path: Path):

# Assert
assert (tmp_path / ".pre-commit-config.yaml").read_text() == ""


class TestReadPreCommitConfigYAML:
def test_quote_style_preserved(self, tmp_path: Path):
# Arrange
content_str = """\
repos:
- repo: 'https://github.com/abravalheri/validate-pyproject'
rev: 'v0.23'
"""

(tmp_path / ".pre-commit-config.yaml").write_text(content_str)

# Act
with change_cwd(tmp_path), read_pre_commit_config_yaml():
pass

# Assert
assert (tmp_path / ".pre-commit-config.yaml").read_text() == content_str