From 9f2ef35dc143a8867b7477ecff5cb214b82728df Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 23 Sep 2024 11:34:33 -0600 Subject: [PATCH 1/6] fix(version): increase `version_variable` flexibility with quotations (ie. json, yaml, etc) Previously json would not work due to the key being wrapped in quotes, yaml also has issues when it does not usually use quotes. The regex we created originally only wrapped the version to be replaced in quotes but now both the key and version can optionally be wrapped in different kind of quotations. Resolves: #601, #706, #962, #1026 --- semantic_release/cli/config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/semantic_release/cli/config.py b/semantic_release/cli/config.py index ba12f4ce4..0b6a1e3c6 100644 --- a/semantic_release/cli/config.py +++ b/semantic_release/cli/config.py @@ -540,7 +540,11 @@ def from_raw_config( # noqa: C901 try: path, variable = decl.split(":", maxsplit=1) # VersionDeclarationABC handles path existence check - search_text = rf"(?x){variable}\s*(:=|[:=])\s*(?P['\"])(?P{SEMVER_REGEX.pattern})(?P=quote)" # noqa: E501 + search_text = str.join("", [ + f"""(?x)(?P['"])?{variable}(?P=quote1)?""", + r"\s*(:=|[:=])\s*", + f"""(?P['"])?(?P{SEMVER_REGEX.pattern})(?P=quote2)?""", + ]) pd = PatternVersionDeclaration(path, search_text) except ValueError as exc: log.exception("Invalid variable declaration %r", decl) From fcee5731bbe341034e382f417931dc76285734b8 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Thu, 26 Sep 2024 22:33:38 -0600 Subject: [PATCH 2/6] docs(configuration): add clarity to `version_variables` usage & limitations Ref: #941 --- docs/configuration.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/configuration.rst b/docs/configuration.rst index e2bfe3bbc..ee63a99b0 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1102,4 +1102,33 @@ specified in ``file:variable`` format. For example: "docs/conf.py:version", ] +Each version variable will be transformed into a Regular Expression that will be used +to substitute the version number in the file. The replacement algorithm is **ONLY** a +pattern match and replace. It will **NOT** evaluate the code nor will PSR understand +any internal object structures (ie. ``file:object.version`` will not work). + +.. important:: + The Regular Expression expects a version value to exist in the file to be replaced. + It cannot be an empty string or a non-semver compliant string. If this is the very + first time you are using PSR, we recommend you set the version to ``0.0.0``. This + may become more flexible in the future with resolution of issue `#941`_. + +.. _#941: https://github.com/python-semantic-release/python-semantic-release/issues/941 + +Given the pattern matching nature of this feature, the Regular Expression is able to +support most file formats as a variable declaration in most languages is very similar. +We specifically support Python, YAML, and JSON as these have been the most common +requests. This configuration option will also work regardless of file extension +because its only a pattern match. + +.. note:: + This will also work for TOML but we recommend using :ref:`config-version_toml` for + TOML files as it actually will interpret the TOML file and replace the version + number before writing the file back to disk. + +.. warning:: + If the file (ex. JSON) you are replacing has two of the same variable name in it, + this pattern match will not be able to differentiate between the two and will replace + both. This is a limitation of the pattern matching and not a bug. + **Default:** ``[]`` From a6eb868c34588e4d9c0539f2594f39de72af607e Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 27 Sep 2024 00:27:24 -0600 Subject: [PATCH 3/6] fix(version-cmd): ensure `version_variables` do not match partial variable names --- semantic_release/cli/config.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/semantic_release/cli/config.py b/semantic_release/cli/config.py index 0b6a1e3c6..5c9f31ef7 100644 --- a/semantic_release/cli/config.py +++ b/semantic_release/cli/config.py @@ -540,11 +540,18 @@ def from_raw_config( # noqa: C901 try: path, variable = decl.split(":", maxsplit=1) # VersionDeclarationABC handles path existence check - search_text = str.join("", [ - f"""(?x)(?P['"])?{variable}(?P=quote1)?""", - r"\s*(:=|[:=])\s*", - f"""(?P['"])?(?P{SEMVER_REGEX.pattern})(?P=quote2)?""", - ]) + search_text = str.join( + "", + [ + # Supports optional matching quotations around variable name + # Negative lookbehind to ensure we don't match part of a variable name + f"""(?x)(?P['"])?(?['"])?(?P{SEMVER_REGEX.pattern})(?P=quote2)?""", + ], + ) pd = PatternVersionDeclaration(path, search_text) except ValueError as exc: log.exception("Invalid variable declaration %r", decl) From 20993c264a225c3913d591c620e9a312c9a257d9 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 27 Sep 2024 00:28:16 -0600 Subject: [PATCH 4/6] build(deps-test): add `PyYAML` as a test dependency --- pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 242021dfe..36be341bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ docs = [ ] test = [ "coverage[toml] ~= 7.0", + "pyyaml ~= 6.0", "pytest ~= 8.3", "pytest-env ~= 1.0", "pytest-xdist ~= 3.0", @@ -86,8 +87,8 @@ env = [ ] addopts = [ # TO DEBUG in single process, swap auto to 0 - "-nauto", - # "-n0", + # "-nauto", + "-n0", "-ra", "--diff-symbols", "--cache-clear", From ed2d27218ffcde529da73ff96615eee480cd6fdd Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 27 Sep 2024 00:29:03 -0600 Subject: [PATCH 5/6] test(fixtures): refactor location of fixture for global use of cli runner --- tests/command_line/conftest.py | 6 ------ tests/conftest.py | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/command_line/conftest.py b/tests/command_line/conftest.py index b8160373d..46d68d46e 100644 --- a/tests/command_line/conftest.py +++ b/tests/command_line/conftest.py @@ -6,7 +6,6 @@ from unittest.mock import MagicMock import pytest -from click.testing import CliRunner from requests_mock import ANY from semantic_release.cli import config as cli_config_module @@ -40,11 +39,6 @@ class RetrieveRuntimeContextFn(Protocol): def __call__(self, repo: Repo) -> RuntimeContext: ... -@pytest.fixture -def cli_runner() -> CliRunner: - return CliRunner(mix_stderr=False) - - @pytest.fixture def post_mocker(requests_mock: Mocker) -> Mocker: """Patch all POST requests, mocking a response body for VCS release creation.""" diff --git a/tests/conftest.py b/tests/conftest.py index 3465b236a..911906a7d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING import pytest +from click.testing import CliRunner from git import Commit, Repo from tests.fixtures import * @@ -28,6 +29,11 @@ class TeardownCachedDirFn(Protocol): def __call__(self, directory: Path) -> Path: ... +@pytest.fixture +def cli_runner() -> CliRunner: + return CliRunner(mix_stderr=False) + + @pytest.fixture(scope="session") def default_netrc_username() -> str: return "username" From b339b6d5d8b790dd5347b4731f38ba042fdd559c Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 27 Sep 2024 00:32:52 -0600 Subject: [PATCH 6/6] test(stamp-version): add test cases to stamp json, python, & yaml files --- tests/scenario/test_version_stamp.py | 219 +++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 tests/scenario/test_version_stamp.py diff --git a/tests/scenario/test_version_stamp.py b/tests/scenario/test_version_stamp.py new file mode 100644 index 000000000..60a408e67 --- /dev/null +++ b/tests/scenario/test_version_stamp.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING + +import pytest +import yaml + +from semantic_release.cli.commands.main import main + +from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD +from tests.fixtures.repos.trunk_based_dev.repo_w_no_tags import ( + repo_with_no_tags_angular_commits, +) +from tests.util import assert_successful_exit_code + +if TYPE_CHECKING: + from click.testing import CliRunner + + from tests.fixtures.example_project import UpdatePyprojectTomlFn + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_python( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + new_version = "0.1.0" + target_file = Path("src/example/_version.py") + + # Set configuration to modify the python file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:__version__"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Load python module for reading the version (ensures the file is valid) + spec = importlib.util.spec_from_file_location("example._version", str(target_file)) + module = importlib.util.module_from_spec(spec) # type: ignore + spec.loader.exec_module(module) # type: ignore + + # Check the version was updated + assert new_version == module.__version__ + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_yaml( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("example.yml") + orig_yaml = dedent( + f"""\ + --- + package: example + version: {orig_version} + date-released: 1970-01-01 + """ + ) + # Write initial text in file + target_file.write_text(orig_yaml) + + # Set configuration to modify the yaml file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_yaml_obj = yaml.safe_load(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_yaml_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_yaml_obj["version"] = orig_version + + assert yaml.safe_load(orig_yaml) == resulting_yaml_obj + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_yaml_cff( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("CITATION.cff") + # Derived format from python-semantic-release/python-semantic-release#962 + orig_yaml = dedent( + f"""\ + --- + cff-version: 1.2.0 + message: "If you use this software, please cite it as below." + authors: + - family-names: Doe + given-names: Jon + orcid: https://orcid.org/1234-6666-2222-5555 + title: "My Research Software" + version: {orig_version} + date-released: 1970-01-01 + """ + ) + # Write initial text in file + target_file.write_text(orig_yaml) + + # Set configuration to modify the yaml file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_yaml_obj = yaml.safe_load(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_yaml_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_yaml_obj["version"] = orig_version + + assert yaml.safe_load(orig_yaml) == resulting_yaml_obj + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_json( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("plugins.json") + orig_json = { + "id": "test-plugin", + "version": orig_version, + "meta": { + "description": "Test plugin", + }, + } + # Write initial text in file + target_file.write_text(json.dumps(orig_json, indent=4)) + + # Set configuration to modify the json file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_json_obj = json.loads(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_json_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_json_obj["version"] = orig_version + + assert orig_json == resulting_json_obj pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy