diff --git a/pyproject.toml b/pyproject.toml index 7dd7607aa..8f1071d59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ "requests ~= 2.25", "jinja2 ~= 3.1", "python-gitlab >= 4.0.0, < 9.0.0", - "tomlkit ~= 0.13.0", + "tomlkit >= 0.13.0, < 0.16.0", "dotty-dict ~= 1.3", "importlib-resources ~= 6.0", "pydantic ~= 2.0", @@ -57,7 +57,7 @@ repository = "http://github.com/python-semantic-release/python-semantic-release. [project.optional-dependencies] build = [ "build ~= 1.2", - "tomlkit ~= 0.13.0", + "tomlkit >= 0.13.0, < 0.16.0", ] docs = [ "Sphinx ~= 7.4", @@ -80,7 +80,7 @@ test = [ "pytest-order ~= 1.3", "pytest-pretty ~= 1.2", "pytest-xdist ~= 3.0", - "responses ~= 0.25.0", + "responses ~= 0.26.2", "requests-mock ~= 1.10", ] dev = [ diff --git a/src/semantic_release/cli/cli_context.py b/src/semantic_release/cli/cli_context.py index a5c07a85e..502411f2b 100644 --- a/src/semantic_release/cli/cli_context.py +++ b/src/semantic_release/cli/cli_context.py @@ -67,7 +67,7 @@ def _init_raw_config(self) -> RawConfig: ) ) - # TODO: Evaluate Exeception catches + # TODO: Evaluate Exception catches try: if was_conf_file_user_provided and not conf_file_exists: raise FileNotFoundError( # noqa: TRY301 diff --git a/src/semantic_release/version/declarations/toml.py b/src/semantic_release/version/declarations/toml.py index 59e6996ac..bcf0fc180 100644 --- a/src/semantic_release/version/declarations/toml.py +++ b/src/semantic_release/version/declarations/toml.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Dict, cast +from typing import TYPE_CHECKING import tomlkit from deprecated.sphinx import deprecated @@ -13,6 +13,9 @@ from semantic_release.version.declarations.i_version_replacer import IVersionReplacer from semantic_release.version.version import Version +if TYPE_CHECKING: + from tomlkit.toml_document import TOMLDocument + class TomlVersionDeclaration(IVersionReplacer): def __init__( @@ -47,8 +50,8 @@ def content(self) -> None: def parse(self) -> set[Version]: # pragma: no cover """Look for the version in the source content""" content = self._load() - maybe_version: str = content.get(self._search_text) # type: ignore[return-value] - if maybe_version is not None: + maybe_version = content.get(self._search_text) + if isinstance(maybe_version, str) and (maybe_version := maybe_version.strip()): logger.debug( "Found a key %r that looks like a version (%r)", self._search_text, @@ -65,23 +68,34 @@ def replace(self, new_version: Version) -> str: updated content. """ content = self._load() - if self._search_text in content: - logger.info( - "found %r in source file contents, replacing with %s", - self._search_text, - new_version, - ) - content[self._search_text] = ( - new_version.as_tag() - if self._stamp_format == VersionStampType.TAG_FORMAT - else str(new_version) - ) - - return tomlkit.dumps(cast(Dict[str, Any], content)) - - def _load(self) -> Dotty: - """Load the content of the source file into a Dotty for easier searching""" - return Dotty(tomlkit.loads(self.content)) + if self._search_text not in Dotty(content): + # TODO: v11 + # raise KeyError( + # f"key {self._search_text!r} not found in source file {self._path!r}" + # ) + return tomlkit.dumps(content) + + logger.info( + "found %r in source file contents, replacing with %s", + self._search_text, + new_version, + ) + + sub_content = content + parts = self._search_text.split(".") + for part in parts[:-1]: + sub_content = sub_content.get(part, {}) + + sub_content[parts[-1]] = ( + new_version.as_tag() + if self._stamp_format == VersionStampType.TAG_FORMAT + else str(new_version) + ) + + return tomlkit.dumps(content) + + def _load(self) -> TOMLDocument: + return tomlkit.loads(self.content) def update_file_w_version( self, new_version: Version, noop: bool = False @@ -93,7 +107,7 @@ def update_file_w_version( ) return None - if self._search_text not in self._load(): + if self._search_text not in Dotty(self._load()): noop_report( f"VERSION PATTERN NOT FOUND: no version to stamp in file {self._path!r}", ) diff --git a/tests/conftest.py b/tests/conftest.py index df4e2f82d..4e78b59b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -125,9 +125,9 @@ def pytest_configure(config: pytest.Config): See `pytest_collection_modifyitems` for more information on test selection modifications. """ - user_desired_comprehensive_evaluation = config.getoption("--comprehensive") - user_provided_filter = str(config.getoption("-k")) - user_provided_markers = str(config.getoption("-m")) + user_desired_comprehensive_evaluation = bool(config.getoption("--comprehensive")) + user_provided_filter = str(config.getoption("-k")).strip() + user_provided_markers = str(config.getoption("-m")).strip() root_test_dir = Path(__file__).parent.relative_to(config.rootpath) user_provided_test_path = bool(config.args != [str(root_test_dir)]) @@ -183,7 +183,7 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item comprehensive_test_skip_marker = pytest.mark.skip( reason="comprehensive tests are disabled by default" ) - user_provided_filter = str(config.getoption("-k")) + user_provided_filter = str(config.getoption("-k")).strip() if any((disable_comprehensive_tests,)): for item in items: diff --git a/tests/unit/semantic_release/version/declarations/test_toml_declaration.py b/tests/unit/semantic_release/version/declarations/test_toml_declaration.py index a768b6cd3..f90e6dec3 100644 --- a/tests/unit/semantic_release/version/declarations/test_toml_declaration.py +++ b/tests/unit/semantic_release/version/declarations/test_toml_declaration.py @@ -13,6 +13,7 @@ from semantic_release.version.declarations.toml import TomlVersionDeclaration from semantic_release.version.version import Version +from tests.fixtures.example_project import change_to_ex_proj_dir from tests.fixtures.git_repo import default_tag_format_str if TYPE_CHECKING: @@ -115,6 +116,7 @@ def test_toml_declaration_is_version_replacer(): ] ], ) +@pytest.mark.usefixtures(change_to_ex_proj_dir.__name__) def test_toml_declaration_from_definition( replacement_def: str, tag_format: str, @@ -122,7 +124,6 @@ def test_toml_declaration_from_definition( resulting_contents: str, next_version: str, test_file: str, - change_to_ex_proj_dir: None, ): """ Given a file with a formatted version string, @@ -152,9 +153,8 @@ def test_toml_declaration_from_definition( assert expected_filepath == actual_file_modified -def test_toml_declaration_no_file_change( - change_to_ex_proj_dir: None, -): +@pytest.mark.usefixtures(change_to_ex_proj_dir.__name__) +def test_toml_declaration_no_file_change(): """ Given a configured stamp file is already up-to-date, When update_file_w_version() is called with the same version, @@ -202,9 +202,8 @@ def test_toml_declaration_error_on_missing_file(): ) -def test_toml_declaration_no_version_in_file( - change_to_ex_proj_dir: None, -): +@pytest.mark.usefixtures(change_to_ex_proj_dir.__name__) +def test_toml_declaration_no_version_in_file(): test_file = "test_file" expected_filepath = Path(test_file).resolve() starting_contents = dedent( @@ -233,9 +232,8 @@ def test_toml_declaration_no_version_in_file( assert starting_contents == actual_contents -def test_toml_declaration_noop_is_noop( - change_to_ex_proj_dir: None, -): +@pytest.mark.usefixtures(change_to_ex_proj_dir.__name__) +def test_toml_declaration_noop_is_noop(): test_file = "test_file" expected_filepath = Path(test_file).resolve() starting_contents = dedent( @@ -285,9 +283,9 @@ def test_toml_declaration_noop_warning_on_missing_file( ) +@pytest.mark.usefixtures(change_to_ex_proj_dir.__name__) def test_toml_declaration_noop_warning_on_no_version_in_file( capsys: pytest.CaptureFixture[str], - change_to_ex_proj_dir: None, ): test_file = "test_file" starting_contents = dedent(