From 2ac902a8c23f7beba1b2627eeedb707df8b972e7 Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 11:41:40 +0100 Subject: [PATCH 01/16] feat: tox implementation --- python-utils/update_version.py | 268 +++++++++++++++++++++++++++++++++ tox.ini | 70 +++++++++ 2 files changed, 338 insertions(+) create mode 100644 python-utils/update_version.py create mode 100644 tox.ini diff --git a/python-utils/update_version.py b/python-utils/update_version.py new file mode 100644 index 000000000..2129e793b --- /dev/null +++ b/python-utils/update_version.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python +# Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Update version references across the ansys/actions repository. + +This script updates version references in: +- VERSION file +- .ci/ansys-actions-flit/pyproject.toml ([project].version) +- .ci/ansys-actions-poetry/pyproject.toml ([tool.poetry].version) +- All action.yml files (ansys/actions/*@vX.Y.Z references) + +Usage: + python update_version.py + python update_version.py --dry-run + +Examples: + python update_version.py 10.2.6 + python update_version.py 10.2.6 --dry-run +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +# tomllib is available in Python 3.11+, use tomli for older versions +try: + import tomllib +except ImportError: + import tomli as tomllib + +import tomli_w + + +def get_project_root() -> Path: + """Get the project root directory (parent of python-utils).""" + return Path(__file__).resolve().parent.parent + + +def read_current_version(project_root: Path) -> str: + """Read the current version from the VERSION file.""" + version_file = project_root / "VERSION" + return version_file.read_text().strip() + + +def validate_version(version: str) -> bool: + """Validate that the version string matches semantic versioning pattern.""" + pattern = r"^\d+\.\d+\.\d+$" + return bool(re.match(pattern, version)) + + +def update_version_file(project_root: Path, new_version: str, dry_run: bool = False) -> bool: + """Update the VERSION file with the new version.""" + version_file = project_root / "VERSION" + if dry_run: + print(f" [DRY RUN] Would update {version_file} to: {new_version}") + return True + + version_file.write_text(f"{new_version}\n") + print(f" Updated {version_file}") + return True + + +def update_pyproject_flit( + project_root: Path, old_version: str, new_version: str, dry_run: bool = False +) -> bool: + """Update version in .ci/ansys-actions-flit/pyproject.toml.""" + pyproject_path = project_root / ".ci" / "ansys-actions-flit" / "pyproject.toml" + + if not pyproject_path.exists(): + print(f" Warning: {pyproject_path} not found, skipping") + return False + + with open(pyproject_path, "rb") as f: + data = tomllib.load(f) + + current = data.get("project", {}).get("version", "") + if current != old_version: + print(f" Warning: Expected version {old_version} in {pyproject_path}, found {current}") + + data["project"]["version"] = new_version + + if dry_run: + print(f" [DRY RUN] Would update {pyproject_path}: version = {new_version}") + return True + + with open(pyproject_path, "wb") as f: + tomli_w.dump(data, f) + print(f" Updated {pyproject_path}") + return True + + +def update_pyproject_poetry( + project_root: Path, old_version: str, new_version: str, dry_run: bool = False +) -> bool: + """Update version in .ci/ansys-actions-poetry/pyproject.toml.""" + pyproject_path = project_root / ".ci" / "ansys-actions-poetry" / "pyproject.toml" + + if not pyproject_path.exists(): + print(f" Warning: {pyproject_path} not found, skipping") + return False + + with open(pyproject_path, "rb") as f: + data = tomllib.load(f) + + current = data.get("tool", {}).get("poetry", {}).get("version", "") + if current != old_version: + print(f" Warning: Expected version {old_version} in {pyproject_path}, found {current}") + + data["tool"]["poetry"]["version"] = new_version + + if dry_run: + print(f" [DRY RUN] Would update {pyproject_path}: version = {new_version}") + return True + + with open(pyproject_path, "wb") as f: + tomli_w.dump(data, f) + print(f" Updated {pyproject_path}") + return True + + +def find_action_files(project_root: Path) -> list[Path]: + """Find all action.yml files in the repository.""" + action_files = [] + for action_yml in project_root.rglob("action.yml"): + # Skip any action files in .tox, .git, or other build directories + parts = action_yml.parts + if any(part.startswith(".") and part not in (".ci",) for part in parts): + if ".git" in parts or ".tox" in parts: + continue + action_files.append(action_yml) + return sorted(action_files) + + +def update_action_file( + action_file: Path, old_version: str, new_version: str, dry_run: bool = False +) -> int: + """Update ansys/actions references in an action.yml file. + + Returns the number of replacements made. + """ + content = action_file.read_text() + + # Pattern to match: ansys/actions/@v + old_pattern = f"ansys/actions/([^@]+)@v{re.escape(old_version)}" + new_replacement = f"ansys/actions/\\1@v{new_version}" + + new_content, count = re.subn(old_pattern, new_replacement, content) + + if count > 0: + if dry_run: + print(f" [DRY RUN] Would update {action_file}: {count} reference(s)") + else: + action_file.write_text(new_content) + print(f" Updated {action_file}: {count} reference(s)") + + return count + + +def main() -> int: + """Main entry point for the version update script.""" + parser = argparse.ArgumentParser( + description="Update version references across the ansys/actions repository.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python update_version.py 10.2.6 + python update_version.py 10.2.6 --dry-run + """, + ) + parser.add_argument( + "new_version", + help="The new version to set (e.g., 10.2.6)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be changed without making actual changes", + ) + + args = parser.parse_args() + + # Validate the new version format + if not validate_version(args.new_version): + print(f"Error: Invalid version format '{args.new_version}'. Expected format: X.Y.Z") + return 1 + + project_root = get_project_root() + old_version = read_current_version(project_root) + + print(f"Updating version from {old_version} to {args.new_version}") + if args.dry_run: + print("(DRY RUN - no changes will be made)\n") + else: + print() + + # Check if versions are the same + if old_version == args.new_version: + print(f"Warning: New version ({args.new_version}) is the same as current version") + return 0 + + # Track success + all_success = True + total_action_refs = 0 + + # 1. Update VERSION file + print("1. Updating VERSION file...") + if not update_version_file(project_root, args.new_version, args.dry_run): + all_success = False + + # 2. Update flit pyproject.toml + print("\n2. Updating .ci/ansys-actions-flit/pyproject.toml...") + if not update_pyproject_flit(project_root, old_version, args.new_version, args.dry_run): + all_success = False + + # 3. Update poetry pyproject.toml + print("\n3. Updating .ci/ansys-actions-poetry/pyproject.toml...") + if not update_pyproject_poetry(project_root, old_version, args.new_version, args.dry_run): + all_success = False + + # 4. Update all action.yml files + print("\n4. Updating action.yml files...") + action_files = find_action_files(project_root) + files_updated = 0 + for action_file in action_files: + count = update_action_file(action_file, old_version, args.new_version, args.dry_run) + if count > 0: + files_updated += 1 + total_action_refs += count + + if total_action_refs == 0: + print(" No action references found to update") + + # Summary + print("\n" + "=" * 60) + print("Summary:") + print(" - VERSION file: updated") + print(" - pyproject.toml files: 2 updated") + print(f" - action.yml files: {files_updated} files, {total_action_refs} references") + if args.dry_run: + print("\n(DRY RUN - no actual changes were made)") + + return 0 if all_success else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..3f9cb2241 --- /dev/null +++ b/tox.ini @@ -0,0 +1,70 @@ +[tox] +description = Automation environments for ansys/actions repository +envlist = + code-style + doc + tests +skip_missing_interpreters = true +isolated_build = false + +[testenv] +description = Base test environment +basepython = python3.13 +setenv = + PYTHONUNBUFFERED = yes +passenv = + PIP_EXTRA_INDEX_URL + +[testenv:code-style] +description = Run code style checks via prek +skip_install = true +deps = + prek +commands = + prek run --all-files --show-diff-on-failure + +[testenv:doc] +description = Build Sphinx documentation +skip_install = true +deps = + -r{toxinidir}/requirements/requirements_doc.txt +setenv = + SOURCE_DIR = doc/source + BUILD_DIR = doc/_build + BUILDER = html + BUILDER_OPTS = --color -v -j auto -W --keep-going +commands = + sphinx-build -d "{toxworkdir}/doc_doctree" {env:SOURCE_DIR} "{toxinidir}/{env:BUILD_DIR}/{env:BUILDER}" {env:BUILDER_OPTS} -b {env:BUILDER} + +[testenv:doc-clean] +description = Clean documentation build artifacts +skip_install = true +deps = +commands = + python -c "import shutil, sys; shutil.rmtree(sys.argv[1], ignore_errors=True)" "{toxinidir}/doc/_build" + +[testenv:doc-serve] +description = Serve documentation locally for preview +skip_install = true +deps = + sphinx-theme-builder +commands = + stb serve "{toxinidir}/doc/source/" + +[testenv:update-version] +description = Update CI/CD version references to a new tag (usage: tox -e update-version -- 10.1.2) +skip_install = true +deps = + tomli + tomli_w +commands = + python {toxinidir}/python-utils/update_version.py {posargs} + +[testenv:tests] +description = Run Python utility tests +skip_install = true +deps = + pytest + packaging +commands = + pytest -vv {toxinidir}/python-utils {posargs} From 42384a19d0756692fb846343917f211ef0899beb Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:47:05 +0000 Subject: [PATCH 02/16] chore: adding changelog file 1163.added.md [dependabot-skip] --- doc/source/changelog/1163.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/source/changelog/1163.added.md diff --git a/doc/source/changelog/1163.added.md b/doc/source/changelog/1163.added.md new file mode 100644 index 000000000..458642a9e --- /dev/null +++ b/doc/source/changelog/1163.added.md @@ -0,0 +1 @@ +Tox implementation From e3f45d6fe78804f9ab492879fef824791c9e768a Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 13:44:31 +0100 Subject: [PATCH 03/16] fix: codestyle --- python-utils/update_version.py | 40 ++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/python-utils/update_version.py b/python-utils/update_version.py index 2129e793b..ce7824be4 100644 --- a/python-utils/update_version.py +++ b/python-utils/update_version.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -# Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates. + +# Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates. # SPDX-License-Identifier: MIT # -# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights @@ -70,7 +70,9 @@ def validate_version(version: str) -> bool: return bool(re.match(pattern, version)) -def update_version_file(project_root: Path, new_version: str, dry_run: bool = False) -> bool: +def update_version_file( + project_root: Path, new_version: str, dry_run: bool = False +) -> bool: """Update the VERSION file with the new version.""" version_file = project_root / "VERSION" if dry_run: @@ -97,7 +99,9 @@ def update_pyproject_flit( current = data.get("project", {}).get("version", "") if current != old_version: - print(f" Warning: Expected version {old_version} in {pyproject_path}, found {current}") + print( + f" Warning: Expected version {old_version} in {pyproject_path}, found {current}" + ) data["project"]["version"] = new_version @@ -126,7 +130,9 @@ def update_pyproject_poetry( current = data.get("tool", {}).get("poetry", {}).get("version", "") if current != old_version: - print(f" Warning: Expected version {old_version} in {pyproject_path}, found {current}") + print( + f" Warning: Expected version {old_version} in {pyproject_path}, found {current}" + ) data["tool"]["poetry"]["version"] = new_version @@ -203,7 +209,9 @@ def main() -> int: # Validate the new version format if not validate_version(args.new_version): - print(f"Error: Invalid version format '{args.new_version}'. Expected format: X.Y.Z") + print( + f"Error: Invalid version format '{args.new_version}'. Expected format: X.Y.Z" + ) return 1 project_root = get_project_root() @@ -217,7 +225,9 @@ def main() -> int: # Check if versions are the same if old_version == args.new_version: - print(f"Warning: New version ({args.new_version}) is the same as current version") + print( + f"Warning: New version ({args.new_version}) is the same as current version" + ) return 0 # Track success @@ -231,12 +241,16 @@ def main() -> int: # 2. Update flit pyproject.toml print("\n2. Updating .ci/ansys-actions-flit/pyproject.toml...") - if not update_pyproject_flit(project_root, old_version, args.new_version, args.dry_run): + if not update_pyproject_flit( + project_root, old_version, args.new_version, args.dry_run + ): all_success = False # 3. Update poetry pyproject.toml print("\n3. Updating .ci/ansys-actions-poetry/pyproject.toml...") - if not update_pyproject_poetry(project_root, old_version, args.new_version, args.dry_run): + if not update_pyproject_poetry( + project_root, old_version, args.new_version, args.dry_run + ): all_success = False # 4. Update all action.yml files @@ -244,7 +258,9 @@ def main() -> int: action_files = find_action_files(project_root) files_updated = 0 for action_file in action_files: - count = update_action_file(action_file, old_version, args.new_version, args.dry_run) + count = update_action_file( + action_file, old_version, args.new_version, args.dry_run + ) if count > 0: files_updated += 1 total_action_refs += count @@ -257,7 +273,9 @@ def main() -> int: print("Summary:") print(" - VERSION file: updated") print(" - pyproject.toml files: 2 updated") - print(f" - action.yml files: {files_updated} files, {total_action_refs} references") + print( + f" - action.yml files: {files_updated} files, {total_action_refs} references" + ) if args.dry_run: print("\n(DRY RUN - no actual changes were made)") From 587b68e8bca00ed5e46f17f60367f41550a50732 Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 16:42:52 +0100 Subject: [PATCH 04/16] feat: switch to click --- python-utils/update_version.py | 264 ++++++++++++++++----------------- tox.ini | 1 + 2 files changed, 127 insertions(+), 138 deletions(-) diff --git a/python-utils/update_version.py b/python-utils/update_version.py index ce7824be4..ec7cf636a 100644 --- a/python-utils/update_version.py +++ b/python-utils/update_version.py @@ -1,5 +1,4 @@ #!/usr/bin/env python - # Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates. # SPDX-License-Identifier: MIT # @@ -27,6 +26,7 @@ - .ci/ansys-actions-flit/pyproject.toml ([project].version) - .ci/ansys-actions-poetry/pyproject.toml ([tool.poetry].version) - All action.yml files (ansys/actions/*@vX.Y.Z references) +- CI/CD workflow files in .github/workflows/ Usage: python update_version.py @@ -39,11 +39,12 @@ from __future__ import annotations -import argparse import re import sys from pathlib import Path +import click + # tomllib is available in Python 3.11+, use tomli for older versions try: import tomllib @@ -61,7 +62,7 @@ def get_project_root() -> Path: def read_current_version(project_root: Path) -> str: """Read the current version from the VERSION file.""" version_file = project_root / "VERSION" - return version_file.read_text().strip() + return version_file.read_text(encoding="utf-8").strip() def validate_version(version: str) -> bool: @@ -76,97 +77,103 @@ def update_version_file( """Update the VERSION file with the new version.""" version_file = project_root / "VERSION" if dry_run: - print(f" [DRY RUN] Would update {version_file} to: {new_version}") + click.echo(f" [DRY RUN] Would update {version_file} to: {new_version}") return True - version_file.write_text(f"{new_version}\n") - print(f" Updated {version_file}") + version_file.write_text(f"{new_version}\n", encoding="utf-8") + click.echo(f" Updated {version_file}") return True -def update_pyproject_flit( - project_root: Path, old_version: str, new_version: str, dry_run: bool = False +def update_pyproject( + pyproject_path: Path, + version_keys: list[str], + old_version: str, + new_version: str, + dry_run: bool = False, ) -> bool: - """Update version in .ci/ansys-actions-flit/pyproject.toml.""" - pyproject_path = project_root / ".ci" / "ansys-actions-flit" / "pyproject.toml" - + """Update version in a pyproject.toml file. + + Parameters + ---------- + pyproject_path : Path + Path to the pyproject.toml file. + version_keys : list[str] + List of keys to traverse to reach the version value. + E.g., ["project", "version"] for [project].version + or ["tool", "poetry", "version"] for [tool.poetry].version + old_version : str + Expected current version (for validation warning). + new_version : str + New version to set. + dry_run : bool + If True, only show what would be changed. + + Returns + ------- + bool + True if successful, False otherwise. + """ if not pyproject_path.exists(): - print(f" Warning: {pyproject_path} not found, skipping") + click.echo(f" Warning: {pyproject_path} not found, skipping") return False - with open(pyproject_path, "rb") as f: - data = tomllib.load(f) + content = pyproject_path.read_text(encoding="utf-8") + data = tomllib.loads(content) - current = data.get("project", {}).get("version", "") - if current != old_version: - print( - f" Warning: Expected version {old_version} in {pyproject_path}, found {current}" - ) + # Navigate to the parent of the version key + current_dict = data + for key in version_keys[:-1]: + current_dict = current_dict[key] - data["project"]["version"] = new_version - - if dry_run: - print(f" [DRY RUN] Would update {pyproject_path}: version = {new_version}") - return True - - with open(pyproject_path, "wb") as f: - tomli_w.dump(data, f) - print(f" Updated {pyproject_path}") - return True - - -def update_pyproject_poetry( - project_root: Path, old_version: str, new_version: str, dry_run: bool = False -) -> bool: - """Update version in .ci/ansys-actions-poetry/pyproject.toml.""" - pyproject_path = project_root / ".ci" / "ansys-actions-poetry" / "pyproject.toml" - - if not pyproject_path.exists(): - print(f" Warning: {pyproject_path} not found, skipping") - return False - - with open(pyproject_path, "rb") as f: - data = tomllib.load(f) - - current = data.get("tool", {}).get("poetry", {}).get("version", "") + version_key = version_keys[-1] + current = current_dict[version_key] if current != old_version: - print( + click.echo( f" Warning: Expected version {old_version} in {pyproject_path}, found {current}" ) - data["tool"]["poetry"]["version"] = new_version + current_dict[version_key] = new_version if dry_run: - print(f" [DRY RUN] Would update {pyproject_path}: version = {new_version}") + click.echo(f" [DRY RUN] Would update {pyproject_path}: version = {new_version}") return True - with open(pyproject_path, "wb") as f: - tomli_w.dump(data, f) - print(f" Updated {pyproject_path}") + output = tomli_w.dumps(data) + pyproject_path.write_text(output, encoding="utf-8") + click.echo(f" Updated {pyproject_path}") return True -def find_action_files(project_root: Path) -> list[Path]: - """Find all action.yml files in the repository.""" - action_files = [] +def find_action_and_workflow_files(project_root: Path) -> list[Path]: + """Find all action.yml files and CI/CD workflow files in the repository.""" + files = [] + + # Find all action.yml files for action_yml in project_root.rglob("action.yml"): # Skip any action files in .tox, .git, or other build directories parts = action_yml.parts - if any(part.startswith(".") and part not in (".ci",) for part in parts): - if ".git" in parts or ".tox" in parts: - continue - action_files.append(action_yml) - return sorted(action_files) + if ".git" in parts or ".tox" in parts: + continue + files.append(action_yml) + # Find CI/CD workflow files in .github/workflows/ + workflows_dir = project_root / ".github" / "workflows" + if workflows_dir.exists(): + for workflow_file in workflows_dir.glob("ci_cd_*.yml"): + files.append(workflow_file) -def update_action_file( - action_file: Path, old_version: str, new_version: str, dry_run: bool = False + return sorted(files) + + +def update_yaml_file( + yaml_file: Path, old_version: str, new_version: str, dry_run: bool = False ) -> int: - """Update ansys/actions references in an action.yml file. + """Update ansys/actions references in a YAML file. Returns the number of replacements made. """ - content = action_file.read_text() + content = yaml_file.read_text(encoding="utf-8") # Pattern to match: ansys/actions/@v old_pattern = f"ansys/actions/([^@]+)@v{re.escape(old_version)}" @@ -176,111 +183,92 @@ def update_action_file( if count > 0: if dry_run: - print(f" [DRY RUN] Would update {action_file}: {count} reference(s)") + click.echo(f" [DRY RUN] Would update {yaml_file}: {count} reference(s)") else: - action_file.write_text(new_content) - print(f" Updated {action_file}: {count} reference(s)") + yaml_file.write_text(new_content, encoding="utf-8") + click.echo(f" Updated {yaml_file}: {count} reference(s)") return count -def main() -> int: - """Main entry point for the version update script.""" - parser = argparse.ArgumentParser( - description="Update version references across the ansys/actions repository.", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - python update_version.py 10.2.6 - python update_version.py 10.2.6 --dry-run - """, - ) - parser.add_argument( - "new_version", - help="The new version to set (e.g., 10.2.6)", - ) - parser.add_argument( - "--dry-run", - action="store_true", - help="Show what would be changed without making actual changes", - ) - - args = parser.parse_args() +@click.command() +@click.argument("new_version") +@click.option( + "--dry-run", + is_flag=True, + help="Show what would be changed without making actual changes.", +) +def main(new_version: str, dry_run: bool) -> None: + """Update version references across the ansys/actions repository. + + NEW_VERSION is the new version to set (e.g., 10.2.6). + \b + Examples: + python update_version.py 10.2.6 + python update_version.py 10.2.6 --dry-run + """ # Validate the new version format - if not validate_version(args.new_version): - print( - f"Error: Invalid version format '{args.new_version}'. Expected format: X.Y.Z" + if not validate_version(new_version): + raise click.BadParameter( + f"Invalid version format '{new_version}'. Expected format: X.Y.Z", + param_hint="'NEW_VERSION'", ) - return 1 project_root = get_project_root() old_version = read_current_version(project_root) - print(f"Updating version from {old_version} to {args.new_version}") - if args.dry_run: - print("(DRY RUN - no changes will be made)\n") + click.echo(f"Updating version from {old_version} to {new_version}") + if dry_run: + click.echo("(DRY RUN - no changes will be made)\n") else: - print() + click.echo() - # Check if versions are the same - if old_version == args.new_version: - print( - f"Warning: New version ({args.new_version}) is the same as current version" - ) - return 0 + if old_version == new_version: + click.echo(f"Warning: New version ({new_version}) is the same as current version") + sys.exit(0) - # Track success all_success = True - total_action_refs = 0 + total_refs = 0 - # 1. Update VERSION file - print("1. Updating VERSION file...") - if not update_version_file(project_root, args.new_version, args.dry_run): + click.echo("1. Updating VERSION file...") + if not update_version_file(project_root, new_version, dry_run): all_success = False - # 2. Update flit pyproject.toml - print("\n2. Updating .ci/ansys-actions-flit/pyproject.toml...") - if not update_pyproject_flit( - project_root, old_version, args.new_version, args.dry_run - ): + click.echo("\n2. Updating .ci/ansys-actions-flit/pyproject.toml...") + flit_path = project_root / ".ci" / "ansys-actions-flit" / "pyproject.toml" + if not update_pyproject(flit_path, ["project", "version"], old_version, new_version, dry_run): all_success = False - # 3. Update poetry pyproject.toml - print("\n3. Updating .ci/ansys-actions-poetry/pyproject.toml...") - if not update_pyproject_poetry( - project_root, old_version, args.new_version, args.dry_run + click.echo("\n3. Updating .ci/ansys-actions-poetry/pyproject.toml...") + poetry_path = project_root / ".ci" / "ansys-actions-poetry" / "pyproject.toml" + if not update_pyproject( + poetry_path, ["tool", "poetry", "version"], old_version, new_version, dry_run ): all_success = False - # 4. Update all action.yml files - print("\n4. Updating action.yml files...") - action_files = find_action_files(project_root) + click.echo("\n4. Updating action.yml and workflow files...") + yaml_files = find_action_and_workflow_files(project_root) files_updated = 0 - for action_file in action_files: - count = update_action_file( - action_file, old_version, args.new_version, args.dry_run - ) + for yaml_file in yaml_files: + count = update_yaml_file(yaml_file, old_version, new_version, dry_run) if count > 0: files_updated += 1 - total_action_refs += count + total_refs += count - if total_action_refs == 0: - print(" No action references found to update") + if total_refs == 0: + click.echo(" No action references found to update") - # Summary - print("\n" + "=" * 60) - print("Summary:") - print(" - VERSION file: updated") - print(" - pyproject.toml files: 2 updated") - print( - f" - action.yml files: {files_updated} files, {total_action_refs} references" - ) - if args.dry_run: - print("\n(DRY RUN - no actual changes were made)") + click.echo("\n" + "=" * 60) + click.echo("Summary:") + click.echo(" - VERSION file: updated") + click.echo(" - pyproject.toml files: 2 updated") + click.echo(f" - YAML files: {files_updated} files, {total_refs} references") + if dry_run: + click.echo("\n(DRY RUN - no actual changes were made)") - return 0 if all_success else 1 + sys.exit(0 if all_success else 1) if __name__ == "__main__": - sys.exit(main()) + main() diff --git a/tox.ini b/tox.ini index 3f9cb2241..cdded25e9 100644 --- a/tox.ini +++ b/tox.ini @@ -55,6 +55,7 @@ commands = description = Update CI/CD version references to a new tag (usage: tox -e update-version -- 10.1.2) skip_install = true deps = + click tomli tomli_w commands = From 71c09769a8f228a4393d46ad0241d125ea82bfee Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 16:43:31 +0100 Subject: [PATCH 05/16] fix: codestyle --- python-utils/update_version.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/python-utils/update_version.py b/python-utils/update_version.py index ec7cf636a..d084e2813 100644 --- a/python-utils/update_version.py +++ b/python-utils/update_version.py @@ -1,4 +1,5 @@ #!/usr/bin/env python + # Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates. # SPDX-License-Identifier: MIT # @@ -136,7 +137,9 @@ def update_pyproject( current_dict[version_key] = new_version if dry_run: - click.echo(f" [DRY RUN] Would update {pyproject_path}: version = {new_version}") + click.echo( + f" [DRY RUN] Would update {pyproject_path}: version = {new_version}" + ) return True output = tomli_w.dumps(data) @@ -225,7 +228,9 @@ def main(new_version: str, dry_run: bool) -> None: click.echo() if old_version == new_version: - click.echo(f"Warning: New version ({new_version}) is the same as current version") + click.echo( + f"Warning: New version ({new_version}) is the same as current version" + ) sys.exit(0) all_success = True @@ -237,7 +242,9 @@ def main(new_version: str, dry_run: bool) -> None: click.echo("\n2. Updating .ci/ansys-actions-flit/pyproject.toml...") flit_path = project_root / ".ci" / "ansys-actions-flit" / "pyproject.toml" - if not update_pyproject(flit_path, ["project", "version"], old_version, new_version, dry_run): + if not update_pyproject( + flit_path, ["project", "version"], old_version, new_version, dry_run + ): all_success = False click.echo("\n3. Updating .ci/ansys-actions-poetry/pyproject.toml...") From a51e52a30163aa3bd14e6338cf21f10db9624f66 Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 16:44:46 +0100 Subject: [PATCH 06/16] feat: update tox env names --- tox.ini | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index cdded25e9..b7c95c224 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ deps = commands = prek run --all-files --show-diff-on-failure -[testenv:doc] +[testenv:doc-build] description = Build Sphinx documentation skip_install = true deps = @@ -43,14 +43,6 @@ deps = commands = python -c "import shutil, sys; shutil.rmtree(sys.argv[1], ignore_errors=True)" "{toxinidir}/doc/_build" -[testenv:doc-serve] -description = Serve documentation locally for preview -skip_install = true -deps = - sphinx-theme-builder -commands = - stb serve "{toxinidir}/doc/source/" - [testenv:update-version] description = Update CI/CD version references to a new tag (usage: tox -e update-version -- 10.1.2) skip_install = true From 471ce64be64dc7371adbda2a55d22ae41a65fb3a Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 16:47:01 +0100 Subject: [PATCH 07/16] fix: codestyle --- python-utils/update_version.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python-utils/update_version.py b/python-utils/update_version.py index d084e2813..739b4d533 100644 --- a/python-utils/update_version.py +++ b/python-utils/update_version.py @@ -1,5 +1,4 @@ #!/usr/bin/env python - # Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates. # SPDX-License-Identifier: MIT # From bb5c684f42abf2a0f8dd259d33e21a2320e160ce Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 16:54:36 +0100 Subject: [PATCH 08/16] feat: rename file and update tox.ini --- .../{update_version.py => update_tag_references.py} | 0 tox.ini | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename python-utils/{update_version.py => update_tag_references.py} (100%) diff --git a/python-utils/update_version.py b/python-utils/update_tag_references.py similarity index 100% rename from python-utils/update_version.py rename to python-utils/update_tag_references.py diff --git a/tox.ini b/tox.ini index b7c95c224..3b50af223 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ commands = prek run --all-files --show-diff-on-failure [testenv:doc-build] -description = Build Sphinx documentation +description = Build documentation skip_install = true deps = -r{toxinidir}/requirements/requirements_doc.txt @@ -43,15 +43,15 @@ deps = commands = python -c "import shutil, sys; shutil.rmtree(sys.argv[1], ignore_errors=True)" "{toxinidir}/doc/_build" -[testenv:update-version] -description = Update CI/CD version references to a new tag (usage: tox -e update-version -- 10.1.2) +[testenv:update-tag-references] +description = Update CI/CD version references to a new tag (usage: tox -e update-tag-references -- 10.1.2) skip_install = true deps = click tomli tomli_w commands = - python {toxinidir}/python-utils/update_version.py {posargs} + python {toxinidir}/python-utils/update_tag_references.py {posargs} [testenv:tests] description = Run Python utility tests From 255e4addfb499be34b2bcdaaaf9ea4373229468a Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 16:54:59 +0100 Subject: [PATCH 09/16] fix: codestyle --- python-utils/update_tag_references.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python-utils/update_tag_references.py b/python-utils/update_tag_references.py index 739b4d533..d084e2813 100644 --- a/python-utils/update_tag_references.py +++ b/python-utils/update_tag_references.py @@ -1,4 +1,5 @@ #!/usr/bin/env python + # Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates. # SPDX-License-Identifier: MIT # From aee75698d7a62c51d90cd57db5eaa1e4fcfe80bc Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 16:59:02 +0100 Subject: [PATCH 10/16] feat: update test deps --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 3b50af223..95a081ec0 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,8 @@ description = Automation environments for ansys/actions repository envlist = code-style - doc tests + doc-build skip_missing_interpreters = true isolated_build = false @@ -58,6 +58,5 @@ description = Run Python utility tests skip_install = true deps = pytest - packaging commands = pytest -vv {toxinidir}/python-utils {posargs} From 5af6cfed5c483ed102b7f806e12f1981b1d20772 Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Thu, 19 Feb 2026 17:12:08 +0100 Subject: [PATCH 11/16] fix: remove shebang --- python-utils/update_tag_references.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python-utils/update_tag_references.py b/python-utils/update_tag_references.py index d084e2813..256fe8d73 100644 --- a/python-utils/update_tag_references.py +++ b/python-utils/update_tag_references.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - # Copyright (C) 2022 - 2026 ANSYS, Inc. and/or its affiliates. # SPDX-License-Identifier: MIT # From d7e01864e4aaa8015e97f82c265278f0e6423723 Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Tue, 24 Feb 2026 11:22:24 +0100 Subject: [PATCH 12/16] fix: Jorge's and Sebastien's review comments --- python-utils/update_tag_references.py | 5 +++-- tox.ini | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/python-utils/update_tag_references.py b/python-utils/update_tag_references.py index 256fe8d73..ca838cf41 100644 --- a/python-utils/update_tag_references.py +++ b/python-utils/update_tag_references.py @@ -129,8 +129,9 @@ def update_pyproject( current = current_dict[version_key] if current != old_version: click.echo( - f" Warning: Expected version {old_version} in {pyproject_path}, found {current}" + f" Error: Expected version {old_version} in {pyproject_path}, found {current}" ) + sys.exit(0) current_dict[version_key] = new_version @@ -227,7 +228,7 @@ def main(new_version: str, dry_run: bool) -> None: if old_version == new_version: click.echo( - f"Warning: New version ({new_version}) is the same as current version" + f"Error: New version ({new_version}) is the same as current version" ) sys.exit(0) diff --git a/tox.ini b/tox.ini index 95a081ec0..edad7fc62 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ isolated_build = false [testenv] description = Base test environment -basepython = python3.13 +basepython = python3 setenv = PYTHONUNBUFFERED = yes passenv = @@ -23,25 +23,24 @@ deps = commands = prek run --all-files --show-diff-on-failure -[testenv:doc-build] -description = Build documentation +[testenv:doc-{clean,links,html}] +description = + Environment for + html: html documentation generation + clean: cleaning previously generated html documentation + links: verifying the integrity of external links within the documentation skip_install = true deps = - -r{toxinidir}/requirements/requirements_doc.txt + links,html: -r{toxinidir}/requirements/requirements_doc.txt setenv = SOURCE_DIR = doc/source BUILD_DIR = doc/_build - BUILDER = html BUILDER_OPTS = --color -v -j auto -W --keep-going + links: BUILDER = linkcheck + html: BUILDER = html commands = - sphinx-build -d "{toxworkdir}/doc_doctree" {env:SOURCE_DIR} "{toxinidir}/{env:BUILD_DIR}/{env:BUILDER}" {env:BUILDER_OPTS} -b {env:BUILDER} - -[testenv:doc-clean] -description = Clean documentation build artifacts -skip_install = true -deps = -commands = - python -c "import shutil, sys; shutil.rmtree(sys.argv[1], ignore_errors=True)" "{toxinidir}/doc/_build" + clean: python -c "import shutil, sys; shutil.rmtree(sys.argv[1], ignore_errors=True)" "{toxinidir}/doc/_build" + html,links: sphinx-build -d "{toxworkdir}/doc_doctree" {env:SOURCE_DIR} "{toxinidir}/{env:BUILD_DIR}/{env:BUILDER}" {env:BUILDER_OPTS} -b {env:BUILDER} [testenv:update-tag-references] description = Update CI/CD version references to a new tag (usage: tox -e update-tag-references -- 10.1.2) From ce270589c9da205d9cfec4665074bf40f795c246 Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Tue, 24 Feb 2026 11:35:48 +0100 Subject: [PATCH 13/16] fix: doc strings review comments --- python-utils/update_tag_references.py | 84 ++++++++++++++++++++++++--- tox.ini | 4 +- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/python-utils/update_tag_references.py b/python-utils/update_tag_references.py index ca838cf41..4d73b0387 100644 --- a/python-utils/update_tag_references.py +++ b/python-utils/update_tag_references.py @@ -54,18 +54,46 @@ def get_project_root() -> Path: - """Get the project root directory (parent of python-utils).""" + """Get the project root directory (parent of python-utils). + + Returns + ------- + Path + Path to the project root directory. + """ return Path(__file__).resolve().parent.parent def read_current_version(project_root: Path) -> str: - """Read the current version from the VERSION file.""" + """Read the current version from the VERSION file. + + Parameters + ---------- + project_root : Path + Path to the project root directory. + + Returns + ------- + str + The current version string. + """ version_file = project_root / "VERSION" return version_file.read_text(encoding="utf-8").strip() def validate_version(version: str) -> bool: - """Validate that the version string matches semantic versioning pattern.""" + """Validate that the version string matches semantic versioning pattern. + + Parameters + ---------- + version : str + The version string to validate. + + Returns + ------- + bool + True if the version matches X.Y.Z format, False otherwise. + """ pattern = r"^\d+\.\d+\.\d+$" return bool(re.match(pattern, version)) @@ -73,7 +101,22 @@ def validate_version(version: str) -> bool: def update_version_file( project_root: Path, new_version: str, dry_run: bool = False ) -> bool: - """Update the VERSION file with the new version.""" + """Update the VERSION file with the new version. + + Parameters + ---------- + project_root : Path + Path to the project root directory. + new_version : str + The new version string to write. + dry_run : bool, optional + If True, only show what would be changed. Default is False. + + Returns + ------- + bool + True if successful, False otherwise. + """ version_file = project_root / "VERSION" if dry_run: click.echo(f" [DRY RUN] Would update {version_file} to: {new_version}") @@ -148,7 +191,18 @@ def update_pyproject( def find_action_and_workflow_files(project_root: Path) -> list[Path]: - """Find all action.yml files and CI/CD workflow files in the repository.""" + """Find all action.yml files and CI/CD workflow files in the repository. + + Parameters + ---------- + project_root : Path + Path to the project root directory. + + Returns + ------- + list[Path] + Sorted list of paths to action.yml and ci_cd_*.yml workflow files. + """ files = [] # Find all action.yml files @@ -173,7 +227,21 @@ def update_yaml_file( ) -> int: """Update ansys/actions references in a YAML file. - Returns the number of replacements made. + Parameters + ---------- + yaml_file : Path + Path to the YAML file to update. + old_version : str + The old version string to search for. + new_version : str + The new version string to replace with. + dry_run : bool, optional + If True, only show what would be changed. Default is False. + + Returns + ------- + int + The number of replacements made. """ content = yaml_file.read_text(encoding="utf-8") @@ -227,9 +295,7 @@ def main(new_version: str, dry_run: bool) -> None: click.echo() if old_version == new_version: - click.echo( - f"Error: New version ({new_version}) is the same as current version" - ) + click.echo(f"Error: New version ({new_version}) is the same as current version") sys.exit(0) all_success = True diff --git a/tox.ini b/tox.ini index edad7fc62..b17ffc960 100644 --- a/tox.ini +++ b/tox.ini @@ -24,8 +24,8 @@ commands = prek run --all-files --show-diff-on-failure [testenv:doc-{clean,links,html}] -description = - Environment for +description = + Environment for html: html documentation generation clean: cleaning previously generated html documentation links: verifying the integrity of external links within the documentation From a5829fc0bfa6dc43768d7c41a76a631fbd418511 Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Tue, 24 Feb 2026 12:12:15 +0100 Subject: [PATCH 14/16] fix: exit codes on error --- python-utils/update_tag_references.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python-utils/update_tag_references.py b/python-utils/update_tag_references.py index 4d73b0387..bf5627a56 100644 --- a/python-utils/update_tag_references.py +++ b/python-utils/update_tag_references.py @@ -174,7 +174,7 @@ def update_pyproject( click.echo( f" Error: Expected version {old_version} in {pyproject_path}, found {current}" ) - sys.exit(0) + sys.exit(1) current_dict[version_key] = new_version @@ -296,7 +296,7 @@ def main(new_version: str, dry_run: bool) -> None: if old_version == new_version: click.echo(f"Error: New version ({new_version}) is the same as current version") - sys.exit(0) + sys.exit(1) all_success = True total_refs = 0 From 57f13273bfa7c0b5b1d4673e1b644d6e3788ea0a Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Tue, 24 Feb 2026 14:18:57 +0100 Subject: [PATCH 15/16] fix: improve error handling --- python-utils/update_tag_references.py | 199 ++++++++++++++++++-------- 1 file changed, 139 insertions(+), 60 deletions(-) diff --git a/python-utils/update_tag_references.py b/python-utils/update_tag_references.py index bf5627a56..6d87a1034 100644 --- a/python-utils/update_tag_references.py +++ b/python-utils/update_tag_references.py @@ -53,6 +53,25 @@ import tomli_w +class FileUpdateError(Exception): + """Base exception for all file update operations.""" + + def __init__(self, message: str, file_path: Path | None = None): + self.message = message + self.file_path = file_path + super().__init__(message) + + +class VersionMismatchError(FileUpdateError): + """Version in file doesn't match expected version.""" + + def __init__(self, file_path: Path, expected: str, found: str): + super().__init__( + f"Version mismatch in {file_path}: expected {expected}, found {found}", + file_path, + ) + + def get_project_root() -> Path: """Get the project root directory (parent of python-utils). @@ -100,7 +119,7 @@ def validate_version(version: str) -> bool: def update_version_file( project_root: Path, new_version: str, dry_run: bool = False -) -> bool: +) -> None: """Update the VERSION file with the new version. Parameters @@ -112,19 +131,23 @@ def update_version_file( dry_run : bool, optional If True, only show what would be changed. Default is False. - Returns - ------- - bool - True if successful, False otherwise. + Raises + ------ + FileUpdateError + If the VERSION file cannot be written. """ version_file = project_root / "VERSION" if dry_run: click.echo(f" [DRY RUN] Would update {version_file} to: {new_version}") - return True + return - version_file.write_text(f"{new_version}\n", encoding="utf-8") - click.echo(f" Updated {version_file}") - return True + try: + version_file.write_text(f"{new_version}\n", encoding="utf-8") + click.echo(f" Updated {version_file}") + except OSError as e: + raise FileUpdateError( + f"Failed to write VERSION file: {version_file}", version_file + ) from e def update_pyproject( @@ -133,7 +156,7 @@ def update_pyproject( old_version: str, new_version: str, dry_run: bool = False, -) -> bool: +) -> None: """Update version in a pyproject.toml file. Parameters @@ -151,30 +174,41 @@ def update_pyproject( dry_run : bool If True, only show what would be changed. - Returns - ------- - bool - True if successful, False otherwise. + Raises + ------ + FileUpdateError + If the file cannot be read/written or has invalid TOML. + VersionMismatchError + If the current version doesn't match the expected version. """ if not pyproject_path.exists(): - click.echo(f" Warning: {pyproject_path} not found, skipping") - return False - - content = pyproject_path.read_text(encoding="utf-8") - data = tomllib.loads(content) + raise FileUpdateError(f"File not found: {pyproject_path}") + + try: + content = pyproject_path.read_text(encoding="utf-8") + data = tomllib.loads(content) + except OSError as e: + raise FileUpdateError(f"Failed to read {pyproject_path}", pyproject_path) from e + except tomllib.TOMLDecodeError as e: + raise FileUpdateError( + f"Invalid TOML in {pyproject_path}", pyproject_path + ) from e # Navigate to the parent of the version key - current_dict = data - for key in version_keys[:-1]: - current_dict = current_dict[key] + try: + current_dict = data + for key in version_keys[:-1]: + current_dict = current_dict[key] + + version_key = version_keys[-1] + current = current_dict[version_key] + except KeyError as e: + raise FileUpdateError( + f"Missing version key in {pyproject_path}", pyproject_path + ) from e - version_key = version_keys[-1] - current = current_dict[version_key] if current != old_version: - click.echo( - f" Error: Expected version {old_version} in {pyproject_path}, found {current}" - ) - sys.exit(1) + raise VersionMismatchError(pyproject_path, old_version, current) current_dict[version_key] = new_version @@ -182,12 +216,16 @@ def update_pyproject( click.echo( f" [DRY RUN] Would update {pyproject_path}: version = {new_version}" ) - return True + return - output = tomli_w.dumps(data) - pyproject_path.write_text(output, encoding="utf-8") - click.echo(f" Updated {pyproject_path}") - return True + try: + output = tomli_w.dumps(data) + pyproject_path.write_text(output, encoding="utf-8") + click.echo(f" Updated {pyproject_path}") + except OSError as e: + raise FileUpdateError( + f"Failed to write {pyproject_path}", pyproject_path + ) from e def find_action_and_workflow_files(project_root: Path) -> list[Path]: @@ -242,8 +280,16 @@ def update_yaml_file( ------- int The number of replacements made. + + Raises + ------ + FileUpdateError + If the file cannot be read or written. """ - content = yaml_file.read_text(encoding="utf-8") + try: + content = yaml_file.read_text(encoding="utf-8") + except OSError as e: + raise FileUpdateError(f"Failed to read {yaml_file}", yaml_file) from e # Pattern to match: ansys/actions/@v old_pattern = f"ansys/actions/([^@]+)@v{re.escape(old_version)}" @@ -255,8 +301,16 @@ def update_yaml_file( if dry_run: click.echo(f" [DRY RUN] Would update {yaml_file}: {count} reference(s)") else: - yaml_file.write_text(new_content, encoding="utf-8") - click.echo(f" Updated {yaml_file}: {count} reference(s)") + try: + yaml_file.write_text(new_content, encoding="utf-8") + click.echo(f" Updated {yaml_file}: {count} reference(s)") + except OSError as e: + raise FileUpdateError(f"Failed to write {yaml_file}", yaml_file) from e + else: + if dry_run: + click.echo(f" [DRY RUN] No references to update in {yaml_file}") + else: + click.echo(f" No references to update in {yaml_file}") return count @@ -298,48 +352,73 @@ def main(new_version: str, dry_run: bool) -> None: click.echo(f"Error: New version ({new_version}) is the same as current version") sys.exit(1) - all_success = True - total_refs = 0 + errors: list[FileUpdateError] = [] click.echo("1. Updating VERSION file...") - if not update_version_file(project_root, new_version, dry_run): - all_success = False + try: + update_version_file(project_root, new_version, dry_run) + except FileUpdateError as e: + errors.append(e) click.echo("\n2. Updating .ci/ansys-actions-flit/pyproject.toml...") flit_path = project_root / ".ci" / "ansys-actions-flit" / "pyproject.toml" - if not update_pyproject( - flit_path, ["project", "version"], old_version, new_version, dry_run - ): - all_success = False + try: + update_pyproject( + flit_path, ["project", "version"], old_version, new_version, dry_run + ) + except FileUpdateError as e: + errors.append(e) click.echo("\n3. Updating .ci/ansys-actions-poetry/pyproject.toml...") poetry_path = project_root / ".ci" / "ansys-actions-poetry" / "pyproject.toml" - if not update_pyproject( - poetry_path, ["tool", "poetry", "version"], old_version, new_version, dry_run - ): - all_success = False + try: + update_pyproject( + poetry_path, + ["tool", "poetry", "version"], + old_version, + new_version, + dry_run, + ) + except FileUpdateError as e: + errors.append(e) click.echo("\n4. Updating action.yml and workflow files...") yaml_files = find_action_and_workflow_files(project_root) + total_refs = 0 files_updated = 0 + for yaml_file in yaml_files: - count = update_yaml_file(yaml_file, old_version, new_version, dry_run) - if count > 0: - files_updated += 1 - total_refs += count + try: + count = update_yaml_file(yaml_file, old_version, new_version, dry_run) + if count > 0: + files_updated += 1 + total_refs += count + except FileUpdateError as e: + errors.append(e) if total_refs == 0: - click.echo(" No action references found to update") + click.echo(" No action references found to update across all YAML files.") + # Summary and error report click.echo("\n" + "=" * 60) - click.echo("Summary:") - click.echo(" - VERSION file: updated") - click.echo(" - pyproject.toml files: 2 updated") - click.echo(f" - YAML files: {files_updated} files, {total_refs} references") - if dry_run: - click.echo("\n(DRY RUN - no actual changes were made)") - sys.exit(0 if all_success else 1) + if errors: + click.secho( + f"\nCompleted with {len(errors)} error(s):\n", fg="yellow", bold=True + ) + for i, error in enumerate(errors, 1): + click.secho(f"{i}. {error.message}", fg="red") + if error.file_path: + click.secho(f" File: {error.file_path}", fg="red", dim=True) + sys.exit(1) + else: + click.echo("Summary:") + click.echo(" - VERSION file: updated") + click.echo(" - pyproject.toml files: 2 updated") + click.echo(f" - YAML files: {files_updated} files, {total_refs} references") + if dry_run: + click.echo("\n(DRY RUN - no actual changes were made)") + sys.exit(0) if __name__ == "__main__": From 5499f7cd65d264f9d2d99d72f66508b24be09c8c Mon Sep 17 00:00:00 2001 From: Muhammed Adedigba Date: Mon, 2 Mar 2026 16:17:45 +0100 Subject: [PATCH 16/16] fix: review suggestions --- python-utils/update_tag_references.py | 100 +++++++++++--------------- 1 file changed, 40 insertions(+), 60 deletions(-) diff --git a/python-utils/update_tag_references.py b/python-utils/update_tag_references.py index 6d87a1034..8daa68d83 100644 --- a/python-utils/update_tag_references.py +++ b/python-utils/update_tag_references.py @@ -72,35 +72,11 @@ def __init__(self, file_path: Path, expected: str, found: str): ) -def get_project_root() -> Path: - """Get the project root directory (parent of python-utils). +PROJECT_ROOT = Path(__file__).resolve().parent.parent +VERSION_FILE_PATH = PROJECT_ROOT / "VERSION" - Returns - ------- - Path - Path to the project root directory. - """ - return Path(__file__).resolve().parent.parent - - -def read_current_version(project_root: Path) -> str: - """Read the current version from the VERSION file. - - Parameters - ---------- - project_root : Path - Path to the project root directory. - - Returns - ------- - str - The current version string. - """ - version_file = project_root / "VERSION" - return version_file.read_text(encoding="utf-8").strip() - -def validate_version(version: str) -> bool: +def is_semver(version: str) -> bool: """Validate that the version string matches semantic versioning pattern. Parameters @@ -118,14 +94,14 @@ def validate_version(version: str) -> bool: def update_version_file( - project_root: Path, new_version: str, dry_run: bool = False + version_file_path: Path, new_version: str, dry_run: bool = False ) -> None: """Update the VERSION file with the new version. Parameters ---------- - project_root : Path - Path to the project root directory. + version_file_path : Path + Path to the VERSION file. new_version : str The new version string to write. dry_run : bool, optional @@ -136,17 +112,16 @@ def update_version_file( FileUpdateError If the VERSION file cannot be written. """ - version_file = project_root / "VERSION" if dry_run: - click.echo(f" [DRY RUN] Would update {version_file} to: {new_version}") + click.echo(f" [DRY RUN] Would update {version_file_path} to: {new_version}") return try: - version_file.write_text(f"{new_version}\n", encoding="utf-8") - click.echo(f" Updated {version_file}") + version_file_path.write_text(f"{new_version}\n", encoding="utf-8") + click.echo(f" Updated {version_file_path}") except OSError as e: raise FileUpdateError( - f"Failed to write VERSION file: {version_file}", version_file + f"Failed to write VERSION file: {version_file_path}", version_file_path ) from e @@ -260,19 +235,19 @@ def find_action_and_workflow_files(project_root: Path) -> list[Path]: return sorted(files) -def update_yaml_file( - yaml_file: Path, old_version: str, new_version: str, dry_run: bool = False +def replace_action_refs_in_yaml_file( + yaml_file_path: Path, old_ref: str, new_ref: str, dry_run: bool = False ) -> int: """Update ansys/actions references in a YAML file. Parameters ---------- - yaml_file : Path + yaml_file_path : Path Path to the YAML file to update. - old_version : str - The old version string to search for. - new_version : str - The new version string to replace with. + old_ref : str + The old version reference string to search for. + new_ref : str + The new version reference string to replace with. dry_run : bool, optional If True, only show what would be changed. Default is False. @@ -287,30 +262,34 @@ def update_yaml_file( If the file cannot be read or written. """ try: - content = yaml_file.read_text(encoding="utf-8") + content = yaml_file_path.read_text(encoding="utf-8") except OSError as e: - raise FileUpdateError(f"Failed to read {yaml_file}", yaml_file) from e + raise FileUpdateError(f"Failed to read {yaml_file_path}", yaml_file_path) from e # Pattern to match: ansys/actions/@v - old_pattern = f"ansys/actions/([^@]+)@v{re.escape(old_version)}" - new_replacement = f"ansys/actions/\\1@v{new_version}" + old_pattern = f"ansys/actions/([^@]+)@v{re.escape(old_ref)}" + new_replacement = f"ansys/actions/\\1@v{new_ref}" new_content, count = re.subn(old_pattern, new_replacement, content) if count > 0: if dry_run: - click.echo(f" [DRY RUN] Would update {yaml_file}: {count} reference(s)") + click.echo( + f" [DRY RUN] Would update {yaml_file_path}: {count} reference(s)" + ) else: try: - yaml_file.write_text(new_content, encoding="utf-8") - click.echo(f" Updated {yaml_file}: {count} reference(s)") + yaml_file_path.write_text(new_content, encoding="utf-8") + click.echo(f" Updated {yaml_file_path}: {count} reference(s)") except OSError as e: - raise FileUpdateError(f"Failed to write {yaml_file}", yaml_file) from e + raise FileUpdateError( + f"Failed to write {yaml_file_path}", yaml_file_path + ) from e else: if dry_run: - click.echo(f" [DRY RUN] No references to update in {yaml_file}") + click.echo(f" [DRY RUN] No references to update in {yaml_file_path}") else: - click.echo(f" No references to update in {yaml_file}") + click.echo(f" No references to update in {yaml_file_path}") return count @@ -333,14 +312,13 @@ def main(new_version: str, dry_run: bool) -> None: python update_version.py 10.2.6 --dry-run """ # Validate the new version format - if not validate_version(new_version): + if not is_semver(new_version): raise click.BadParameter( f"Invalid version format '{new_version}'. Expected format: X.Y.Z", param_hint="'NEW_VERSION'", ) - project_root = get_project_root() - old_version = read_current_version(project_root) + old_version = VERSION_FILE_PATH.read_text(encoding="utf-8").strip() click.echo(f"Updating version from {old_version} to {new_version}") if dry_run: @@ -356,12 +334,12 @@ def main(new_version: str, dry_run: bool) -> None: click.echo("1. Updating VERSION file...") try: - update_version_file(project_root, new_version, dry_run) + update_version_file(VERSION_FILE_PATH, new_version, dry_run) except FileUpdateError as e: errors.append(e) click.echo("\n2. Updating .ci/ansys-actions-flit/pyproject.toml...") - flit_path = project_root / ".ci" / "ansys-actions-flit" / "pyproject.toml" + flit_path = PROJECT_ROOT / ".ci" / "ansys-actions-flit" / "pyproject.toml" try: update_pyproject( flit_path, ["project", "version"], old_version, new_version, dry_run @@ -370,7 +348,7 @@ def main(new_version: str, dry_run: bool) -> None: errors.append(e) click.echo("\n3. Updating .ci/ansys-actions-poetry/pyproject.toml...") - poetry_path = project_root / ".ci" / "ansys-actions-poetry" / "pyproject.toml" + poetry_path = PROJECT_ROOT / ".ci" / "ansys-actions-poetry" / "pyproject.toml" try: update_pyproject( poetry_path, @@ -383,13 +361,15 @@ def main(new_version: str, dry_run: bool) -> None: errors.append(e) click.echo("\n4. Updating action.yml and workflow files...") - yaml_files = find_action_and_workflow_files(project_root) + yaml_files = find_action_and_workflow_files(PROJECT_ROOT) total_refs = 0 files_updated = 0 for yaml_file in yaml_files: try: - count = update_yaml_file(yaml_file, old_version, new_version, dry_run) + count = replace_action_refs_in_yaml_file( + yaml_file, old_version, new_version, dry_run + ) if count > 0: files_updated += 1 total_refs += count