From 16ee3bb62fbd8d33ef4bfbd877113a0b9f6efafa Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Tue, 29 Apr 2025 11:07:44 +0200 Subject: [PATCH 01/14] version-check was refactored into nox task and its usage changed --- .github/workflows/checks.yml | 2 +- exasol/toolbox/nox/_package_version.py | 155 ++++++++++++++++++ exasol/toolbox/nox/_shared.py | 4 +- exasol/toolbox/nox/tasks.py | 2 + .../templates/github/workflows/checks.yml | 2 +- exasol/toolbox/version.py | 4 +- pyproject.toml | 1 - 7 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 exasol/toolbox/nox/_package_version.py diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 4578c0f13..3ea7d9b12 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -19,7 +19,7 @@ jobs: uses: ./.github/actions/python-environment - name: Check Version(s) - run: poetry run version-check `poetry run -- python -c "from noxconfig import PROJECT_CONFIG; print(PROJECT_CONFIG.version_file)"` + run: poetry run nox -s version:check -- `poetry run -- python -c "from noxconfig import PROJECT_CONFIG; print(PROJECT_CONFIG.version_file)"` Documentation: name: Docs diff --git a/exasol/toolbox/nox/_package_version.py b/exasol/toolbox/nox/_package_version.py new file mode 100644 index 000000000..bf0c62a3f --- /dev/null +++ b/exasol/toolbox/nox/_package_version.py @@ -0,0 +1,155 @@ +import argparse +import subprocess +import sys +from argparse import ( + ArgumentParser, + Namespace, +) +from collections import namedtuple +from collections.abc import Iterable +from inspect import cleandoc +from pathlib import Path +from shutil import which +from typing import ( + Any, + Dict, + Union, +) + +import nox +from nox import Session + +Version = namedtuple("Version", ["major", "minor", "patch"]) + +_SUCCESS = 0 +_FAILURE = 1 + +# fmt: off +_VERSION_MODULE_TEMPLATE = cleandoc(''' + # ATTENTION: + # This file is generated by exasol/toolbox/nox/_package_version.py when using: + # * either "poetry run -- nox -s project:fix" + # * or "poetry run -- nox version:check --fix" + # Do not edit this file manually! + # If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. + MAJOR = {major} + MINOR = {minor} + PATCH = {patch} + VERSION = f"{{MAJOR}}.{{MINOR}}.{{PATCH}}" + __version__ = VERSION +''') + "\n" +# fmt: on + + +def version_from_string(s: str) -> Version: + """Converts a version string of the following format major.minor.patch to a version object""" + major, minor, patch = (int(number, base=0) for number in s.split(".")) + return Version(major, minor, patch) + + +class CommitHookError(Exception): + """Indicates that this commit hook encountered an error""" + + +def version_from_python_module(path: Path) -> Version: + """Retrieve version information from the `version` module""" + with open(path, encoding="utf-8") as file: + _locals: dict[str, Any] = {} + _globals: dict[str, Any] = {} + exec(file.read(), _locals, _globals) + + try: + version = _globals["VERSION"] + except KeyError as ex: + raise CommitHookError("Couldn't find version within module") from ex + + return version_from_string(version) + + +def version_from_poetry() -> Version: + poetry = which("poetry") + if not poetry: + raise CommitHookError("Couldn't find poetry executable") + + result = subprocess.run( + [poetry, "version", "--no-ansi"], capture_output=True, check=False + ) + version = result.stdout.decode().split()[1] + return version_from_string(version) + + +def write_version_module(version: Version, path: str, exists_ok: bool = True) -> None: + version_file = Path(path) + if version_file.exists() and not exists_ok: + raise CommitHookError(f"Version file [{version_file}] already exists.") + version_file.unlink(missing_ok=True) + with open(version_file, "w", encoding="utf-8") as f: + f.write( + _VERSION_MODULE_TEMPLATE.format( + major=version.major, minor=version.minor, patch=version.patch + ) + ) + + +def _create_parser() -> ArgumentParser: + parser = ArgumentParser( + prog="nox -s version:check", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument("version_module", help="Path to version module") + parser.add_argument("files", nargs="*") + parser.add_argument( + "-d", + "--debug", + action="store_true", + default=False, + help="enabled debug mode for execution.", + ) + parser.add_argument( + "-f", + "--fix", + action="store_true", + default=False, + help="fix instead of check.", + ) + return parser + + +def _main_debug(args: Namespace) -> int: + module_version = version_from_python_module(args.version_module) + poetry_version = version_from_poetry() + + if args.fix: + write_version_module(poetry_version, args.version_module) + + if not module_version == poetry_version: + print( + f"Version in pyproject.toml {poetry_version} and {args.version_module} {module_version} do not match!" + ) + if args.fix: + print( + f"Updating version in file ({args.version_module}) from {module_version} to {poetry_version}" + ) + return _SUCCESS + + return _FAILURE + + return _SUCCESS + + +def _main(args: Namespace) -> int: + try: + return _main_debug(args) + except Exception as ex: + print(f"Error while executing program, details: {ex}", file=sys.stderr) + return _FAILURE + + +@nox.session(name="version:check", python=False) +def version_check(session: Session) -> None: + """""" + parser = _create_parser() + args = parser.parse_args(session.posargs) + entry_point = _main if not args.debug else _main_debug + if entry_point(args): + session.error() diff --git a/exasol/toolbox/nox/_shared.py b/exasol/toolbox/nox/_shared.py index 450ae6edf..bcbcd93f7 100644 --- a/exasol/toolbox/nox/_shared.py +++ b/exasol/toolbox/nox/_shared.py @@ -47,8 +47,8 @@ def _deny_filter(files: Iterable[Path], deny_list: Iterable[str]) -> Iterable[Pa def _version(session: Session, mode: Mode, version_file: Path) -> None: - command = ["version-check"] - command = command if mode == Mode.Check else command + ["--fix"] + command = ["nox", "-s", "version:check"] + command = command if mode == Mode.Check else command + ["--"] + ["--fix"] session.run(*command, f"{version_file}") diff --git a/exasol/toolbox/nox/tasks.py b/exasol/toolbox/nox/tasks.py index ce8f901b7..7076a4443 100644 --- a/exasol/toolbox/nox/tasks.py +++ b/exasol/toolbox/nox/tasks.py @@ -85,5 +85,7 @@ def check(session: Session) -> None: audit ) +from exasol.toolbox.nox._package_version import version_check + # isort: on # fmt: on diff --git a/exasol/toolbox/templates/github/workflows/checks.yml b/exasol/toolbox/templates/github/workflows/checks.yml index cd92cbfa8..22e747337 100644 --- a/exasol/toolbox/templates/github/workflows/checks.yml +++ b/exasol/toolbox/templates/github/workflows/checks.yml @@ -22,7 +22,7 @@ jobs: run: | echo "Please enable the version check by replacing this output with shell command bellow:" echo "" - echo "poetry run -- version-check <>" + echo "poetry run nox -s version:check -- <>" echo "" echo "Note: <> needs to point to the version file of the project (version.py)." exit 1 diff --git a/exasol/toolbox/version.py b/exasol/toolbox/version.py index 78ea25efe..5ba7754c7 100644 --- a/exasol/toolbox/version.py +++ b/exasol/toolbox/version.py @@ -1,7 +1,7 @@ # ATTENTION: -# This file is generated by exasol/toolbox/pre_commit_hooks/package_version.py when using: +# This file is generated by exasol/toolbox/nox/_package_version.py when using: # * either "poetry run -- nox -s project:fix" -# * or "poetry run -- version-check --fix" +# * or "poetry run -- nox version:check --fix" # Do not edit this file manually! # If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. MAJOR = 1 diff --git a/pyproject.toml b/pyproject.toml index 53b3c8f47..3ee8c99d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,6 +110,5 @@ module = [ ignore_errors = true [tool.poetry.plugins."console_scripts"] -version-check = "exasol.toolbox.pre_commit_hooks.package_version:main" tbx = 'exasol.toolbox.tools.tbx:CLI' sphinx-multiversion = 'exasol.toolbox.sphinx.multiversion:main' \ No newline at end of file From 9f67bf32fe24e7cd88d325b4add814838ec56627 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Tue, 29 Apr 2025 11:10:05 +0200 Subject: [PATCH 02/14] deleted pre_commit_hooks/package_version.py --- .../pre_commit_hooks/package_version.py | 149 ------------------ 1 file changed, 149 deletions(-) delete mode 100644 exasol/toolbox/pre_commit_hooks/package_version.py diff --git a/exasol/toolbox/pre_commit_hooks/package_version.py b/exasol/toolbox/pre_commit_hooks/package_version.py deleted file mode 100644 index d2d99def1..000000000 --- a/exasol/toolbox/pre_commit_hooks/package_version.py +++ /dev/null @@ -1,149 +0,0 @@ -import subprocess -import sys -from argparse import ( - ArgumentParser, - Namespace, -) -from collections import namedtuple -from collections.abc import Iterable -from inspect import cleandoc -from pathlib import Path -from shutil import which -from typing import ( - Any, - Dict, - Union, -) - -Version = namedtuple("Version", ["major", "minor", "patch"]) - -_SUCCESS = 0 -_FAILURE = 1 - -# fmt: off -_VERSION_MODULE_TEMPLATE = cleandoc(''' - # ATTENTION: - # This file is generated by exasol/toolbox/pre_commit_hooks/package_version.py when using: - # * either "poetry run -- nox -s project:fix" - # * or "poetry run -- version-check --fix" - # Do not edit this file manually! - # If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. - MAJOR = {major} - MINOR = {minor} - PATCH = {patch} - VERSION = f"{{MAJOR}}.{{MINOR}}.{{PATCH}}" - __version__ = VERSION -''') + "\n" -# fmt: on - - -def version_from_string(s: str) -> Version: - """Converts a version string of the following format major.minor.patch to a version object""" - major, minor, patch = (int(number, base=0) for number in s.split(".")) - return Version(major, minor, patch) - - -class CommitHookError(Exception): - """Indicates that this commit hook encountered an error""" - - -def version_from_python_module(path: Path) -> Version: - """Retrieve version information from the `version` module""" - with open(path, encoding="utf-8") as file: - _locals: dict[str, Any] = {} - _globals: dict[str, Any] = {} - exec(file.read(), _locals, _globals) - - try: - version = _globals["VERSION"] - except KeyError as ex: - raise CommitHookError("Couldn't find version within module") from ex - - return version_from_string(version) - - -def version_from_poetry() -> Version: - poetry = which("poetry") - if not poetry: - raise CommitHookError("Couldn't find poetry executable") - - result = subprocess.run( - [poetry, "version", "--no-ansi"], capture_output=True, check=False - ) - version = result.stdout.decode().split()[1] - return version_from_string(version) - - -def write_version_module(version: Version, path: str, exists_ok: bool = True) -> None: - version_file = Path(path) - if version_file.exists() and not exists_ok: - raise CommitHookError(f"Version file [{version_file}] already exists.") - version_file.unlink(missing_ok=True) - with open(version_file, "w", encoding="utf-8") as f: - f.write( - _VERSION_MODULE_TEMPLATE.format( - major=version.major, minor=version.minor, patch=version.patch - ) - ) - - -def _create_parser() -> ArgumentParser: - parser = ArgumentParser() - parser.add_argument("version_module", help="Path to version module") - parser.add_argument("files", nargs="*") - parser.add_argument( - "-d", - "--debug", - action="store_true", - default=False, - help="enabled debug mode for execution.", - ) - parser.add_argument( - "-f", - "--fix", - action="store_true", - default=False, - help="fix instead of check.", - ) - return parser - - -def _main_debug(args: Namespace) -> int: - module_version = version_from_python_module(args.version_module) - poetry_version = version_from_poetry() - - if args.fix: - write_version_module(poetry_version, args.version_module) - - if not module_version == poetry_version: - print( - f"Version in pyproject.toml {poetry_version} and {args.version_module} {module_version} do not match!" - ) - if args.fix: - print( - f"Updating version in file ({args.version_module}) from {module_version} to {poetry_version}" - ) - return _SUCCESS - - return _FAILURE - - return _SUCCESS - - -def _main(args: Namespace) -> int: - try: - return _main_debug(args) - except Exception as ex: - print(f"Error while executing program, details: {ex}", file=sys.stderr) - return _FAILURE - - -def main(argv: Union[Iterable[str], None] = None) -> int: - parser = _create_parser() - args = parser.parse_args() - entry_point = _main if not args.debug else _main_debug - return entry_point(args) - - -if __name__ == "__main__": - sys.exit(main()) From a1527c130e64e28224a9c38ba4129d6f03455736 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Tue, 29 Apr 2025 11:20:06 +0200 Subject: [PATCH 03/14] fix --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 3ea7d9b12..6d2475a6c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -19,7 +19,7 @@ jobs: uses: ./.github/actions/python-environment - name: Check Version(s) - run: poetry run nox -s version:check -- `poetry run -- python -c "from noxconfig import PROJECT_CONFIG; print(PROJECT_CONFIG.version_file)"` + run: poetry run -- nox -s version:check -- `poetry run -- python -c "from noxconfig import PROJECT_CONFIG; print(PROJECT_CONFIG.version_file)"` Documentation: name: Docs From 282bca122f8915d6face2684913cb1815f1a60f5 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Tue, 29 Apr 2025 14:18:57 +0200 Subject: [PATCH 04/14] resolve conversation --- exasol/toolbox/nox/_package_version.py | 15 +++++---------- exasol/toolbox/nox/_shared.py | 4 ++-- .../toolbox/templates/github/workflows/checks.yml | 2 +- exasol/toolbox/version.py | 4 ++-- .../{{cookiecutter.package_name}}/version.py | 4 ++-- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/exasol/toolbox/nox/_package_version.py b/exasol/toolbox/nox/_package_version.py index bf0c62a3f..750c38d46 100644 --- a/exasol/toolbox/nox/_package_version.py +++ b/exasol/toolbox/nox/_package_version.py @@ -6,15 +6,10 @@ Namespace, ) from collections import namedtuple -from collections.abc import Iterable from inspect import cleandoc from pathlib import Path from shutil import which -from typing import ( - Any, - Dict, - Union, -) +from typing import Any import nox from nox import Session @@ -29,9 +24,9 @@ # ATTENTION: # This file is generated by exasol/toolbox/nox/_package_version.py when using: # * either "poetry run -- nox -s project:fix" - # * or "poetry run -- nox version:check --fix" + # * or "poetry run -- nox version:check -- --fix" # Do not edit this file manually! - # If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. + # If you need to change the version, do so in the pyproject.toml, e.g. by using `poetry version X.Y.Z`. MAJOR = {major} MINOR = {minor} PATCH = {patch} @@ -93,7 +88,7 @@ def write_version_module(version: Version, path: str, exists_ok: bool = True) -> def _create_parser() -> ArgumentParser: parser = ArgumentParser( - prog="nox -s version:check", + prog="nox -s version:check --", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("version_module", help="Path to version module") @@ -122,7 +117,7 @@ def _main_debug(args: Namespace) -> int: if args.fix: write_version_module(poetry_version, args.version_module) - if not module_version == poetry_version: + if module_version != poetry_version: print( f"Version in pyproject.toml {poetry_version} and {args.version_module} {module_version} do not match!" ) diff --git a/exasol/toolbox/nox/_shared.py b/exasol/toolbox/nox/_shared.py index bcbcd93f7..1020617dc 100644 --- a/exasol/toolbox/nox/_shared.py +++ b/exasol/toolbox/nox/_shared.py @@ -47,8 +47,8 @@ def _deny_filter(files: Iterable[Path], deny_list: Iterable[str]) -> Iterable[Pa def _version(session: Session, mode: Mode, version_file: Path) -> None: - command = ["nox", "-s", "version:check"] - command = command if mode == Mode.Check else command + ["--"] + ["--fix"] + command = ["nox", "-s", "version:check", "--"] + command = command if mode == Mode.Check else command + ["--fix"] session.run(*command, f"{version_file}") diff --git a/exasol/toolbox/templates/github/workflows/checks.yml b/exasol/toolbox/templates/github/workflows/checks.yml index 22e747337..7c3d61ead 100644 --- a/exasol/toolbox/templates/github/workflows/checks.yml +++ b/exasol/toolbox/templates/github/workflows/checks.yml @@ -22,7 +22,7 @@ jobs: run: | echo "Please enable the version check by replacing this output with shell command bellow:" echo "" - echo "poetry run nox -s version:check -- <>" + echo "poetry run -- nox -s version:check -- <>" echo "" echo "Note: <> needs to point to the version file of the project (version.py)." exit 1 diff --git a/exasol/toolbox/version.py b/exasol/toolbox/version.py index 5ba7754c7..2423b5120 100644 --- a/exasol/toolbox/version.py +++ b/exasol/toolbox/version.py @@ -1,9 +1,9 @@ # ATTENTION: # This file is generated by exasol/toolbox/nox/_package_version.py when using: # * either "poetry run -- nox -s project:fix" -# * or "poetry run -- nox version:check --fix" +# * or "poetry run -- nox version:check -- --fix" # Do not edit this file manually! -# If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. +# If you need to change the version, do so in the pyproject.toml, e.g. by using `poetry version X.Y.Z`. MAJOR = 1 MINOR = 0 PATCH = 1 diff --git a/project-template/{{cookiecutter.repo_name}}/exasol/{{cookiecutter.package_name}}/version.py b/project-template/{{cookiecutter.repo_name}}/exasol/{{cookiecutter.package_name}}/version.py index 84d43669b..bdaa84aaf 100644 --- a/project-template/{{cookiecutter.repo_name}}/exasol/{{cookiecutter.package_name}}/version.py +++ b/project-template/{{cookiecutter.repo_name}}/exasol/{{cookiecutter.package_name}}/version.py @@ -1,9 +1,9 @@ # ATTENTION: # This file is generated by exasol/toolbox/pre_commit_hooks/package_version.py when using: # * either "poetry run -- nox -s project:fix" -# * or "poetry run -- version-check --fix" +# * or "poetry run -- nox -s version:check -- --fix" # Do not edit this file manually! -# If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`. +# If you need to change the version, do so in the pyproject.toml, e.g. by using `poetry version X.Y.Z`. MAJOR = 0 MINOR = 1 PATCH = 0 From efd22a9e35582abf5b58aba4f4d6a4c890dd5afa Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Tue, 29 Apr 2025 14:19:30 +0200 Subject: [PATCH 05/14] resolve conversation --- doc/changes/unreleased.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index f39adb846..ffba4e546 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -2,4 +2,8 @@ ## ✨ Features -* [#378](https://github.com/exasol/python-toolbox/pull/378/files): Add Nox task to trigger a release \ No newline at end of file +* [#378](https://github.com/exasol/python-toolbox/pull/378/files): Add Nox task to trigger a release + +## ⚒️ Refactorings + +* [#412](https://github.com/exasol/python-toolbox/pull/412): Refactor pre commit hook package version.py into nox task \ No newline at end of file From 73830ba201abd8cc1355965d5ca071b71117c2ed Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Tue, 29 Apr 2025 15:11:41 +0200 Subject: [PATCH 06/14] resolve conversation --- exasol/toolbox/cli.py | 2 +- exasol/toolbox/nox/_package_version.py | 50 +---- exasol/toolbox/nox/_release.py | 6 +- exasol/toolbox/release/__init__.py | 106 +---------- exasol/toolbox/sphinx/multiversion/main.py | 2 +- exasol/toolbox/sphinx/multiversion/sphinx.py | 2 +- exasol/toolbox/version/__init__.py | 123 +++++++++++++ test/unit/cli_test.py | 2 +- test/unit/release_test.py | 182 +------------------ test/unit/version_test.py | 178 ++++++++++++++++++ 10 files changed, 318 insertions(+), 335 deletions(-) create mode 100644 exasol/toolbox/version/__init__.py create mode 100644 test/unit/version_test.py diff --git a/exasol/toolbox/cli.py b/exasol/toolbox/cli.py index 5dbc818a7..f136520b1 100644 --- a/exasol/toolbox/cli.py +++ b/exasol/toolbox/cli.py @@ -1,6 +1,6 @@ from argparse import ArgumentTypeError -from exasol.toolbox.release import Version +from exasol.toolbox.version import Version def version(arg: str) -> Version: diff --git a/exasol/toolbox/nox/_package_version.py b/exasol/toolbox/nox/_package_version.py index 750c38d46..33a3e0439 100644 --- a/exasol/toolbox/nox/_package_version.py +++ b/exasol/toolbox/nox/_package_version.py @@ -1,20 +1,17 @@ import argparse -import subprocess import sys from argparse import ( ArgumentParser, Namespace, ) -from collections import namedtuple from inspect import cleandoc from pathlib import Path -from shutil import which -from typing import Any import nox from nox import Session -Version = namedtuple("Version", ["major", "minor", "patch"]) +from exasol.toolbox.error import ToolboxError +from exasol.toolbox.version import Version _SUCCESS = 0 _FAILURE = 1 @@ -36,47 +33,10 @@ # fmt: on -def version_from_string(s: str) -> Version: - """Converts a version string of the following format major.minor.patch to a version object""" - major, minor, patch = (int(number, base=0) for number in s.split(".")) - return Version(major, minor, patch) - - -class CommitHookError(Exception): - """Indicates that this commit hook encountered an error""" - - -def version_from_python_module(path: Path) -> Version: - """Retrieve version information from the `version` module""" - with open(path, encoding="utf-8") as file: - _locals: dict[str, Any] = {} - _globals: dict[str, Any] = {} - exec(file.read(), _locals, _globals) - - try: - version = _globals["VERSION"] - except KeyError as ex: - raise CommitHookError("Couldn't find version within module") from ex - - return version_from_string(version) - - -def version_from_poetry() -> Version: - poetry = which("poetry") - if not poetry: - raise CommitHookError("Couldn't find poetry executable") - - result = subprocess.run( - [poetry, "version", "--no-ansi"], capture_output=True, check=False - ) - version = result.stdout.decode().split()[1] - return version_from_string(version) - - def write_version_module(version: Version, path: str, exists_ok: bool = True) -> None: version_file = Path(path) if version_file.exists() and not exists_ok: - raise CommitHookError(f"Version file [{version_file}] already exists.") + raise ToolboxError(f"Version file [{version_file}] already exists.") version_file.unlink(missing_ok=True) with open(version_file, "w", encoding="utf-8") as f: f.write( @@ -111,8 +71,8 @@ def _create_parser() -> ArgumentParser: def _main_debug(args: Namespace) -> int: - module_version = version_from_python_module(args.version_module) - poetry_version = version_from_poetry() + module_version = Version.from_python_module(args.version_module) + poetry_version = Version.from_poetry() if args.fix: write_version_module(poetry_version, args.version_module) diff --git a/exasol/toolbox/nox/_release.py b/exasol/toolbox/nox/_release.py index 2d9db7600..4dfe306f6 100644 --- a/exasol/toolbox/nox/_release.py +++ b/exasol/toolbox/nox/_release.py @@ -14,13 +14,15 @@ ) from exasol.toolbox.nox.plugin import NoxTasks from exasol.toolbox.release import ( - ReleaseTypes, - Version, extract_release_notes, new_changelog, new_changes, new_unreleased, ) +from exasol.toolbox.version import ( + ReleaseTypes, + Version, +) from noxconfig import PROJECT_CONFIG diff --git a/exasol/toolbox/release/__init__.py b/exasol/toolbox/release/__init__.py index a8e5b93e6..377f19d32 100644 --- a/exasol/toolbox/release/__init__.py +++ b/exasol/toolbox/release/__init__.py @@ -1,112 +1,10 @@ from __future__ import annotations -import subprocess -from dataclasses import dataclass from datetime import datetime -from enum import Enum -from functools import ( - total_ordering, - wraps, -) from inspect import cleandoc from pathlib import Path -from shutil import which - -from exasol.toolbox.error import ToolboxError - - -def _index_or(container, index, default): - try: - return container[index] - except IndexError: - return default - - -class ReleaseTypes(Enum): - Major = "major" - Minor = "minor" - Patch = "patch" - - def __str__(self): - return self.name.lower() - - -def poetry_command(func): - @wraps(func) - def wrapper(*args, **kwargs): - cmd = which("poetry") - if not cmd: - raise ToolboxError("Couldn't find poetry executable") - try: - return func(*args, **kwargs) - except subprocess.CalledProcessError as ex: - raise ToolboxError(f"Failed to execute: {ex.cmd}") from ex - - return wrapper - - -@total_ordering -@dataclass(frozen=True) -class Version: - major: int - minor: int - patch: int - - def __str__(self): - return f"{self.major}.{self.minor}.{self.patch}" - - def __lt__(self, other: object): - if not isinstance(other, Version): - return NotImplemented - return ( - self.major < other.major - or (self.major <= other.major and self.minor < other.minor) - or ( - self.major <= other.major - and self.minor <= other.minor - and self.patch < other.patch - ) - ) - - def __eq__(self, other: object): - if not isinstance(other, Version): - return NotImplemented - return ( - self.major == other.major - and self.minor == other.minor - and self.patch == other.patch - ) - - @staticmethod - def from_string(version): - parts = [int(number, base=0) for number in version.split(".")] - if len(parts) > 3: - raise ValueError( - "Version has an invalid format, " - f"expected: '..', actual: '{version}'" - ) - version = [_index_or(parts, i, 0) for i in range(3)] - return Version(*version) - - @staticmethod - @poetry_command - def from_poetry(): - output = subprocess.run( - ["poetry", "version", "--no-ansi", "--short"], - capture_output=True, - text=True, - ) - return Version.from_string(output.stdout.strip()) - - @staticmethod - @poetry_command - def upgrade_version_from_poetry(t: ReleaseTypes): - output = subprocess.run( - ["poetry", "version", str(t), "--dry-run", "--no-ansi", "--short"], - capture_output=True, - text=True, - ) - return Version.from_string(output.stdout.strip()) + +from exasol.toolbox.version import Version def extract_release_notes(file: str | Path) -> str: diff --git a/exasol/toolbox/sphinx/multiversion/main.py b/exasol/toolbox/sphinx/multiversion/main.py index 694fd258a..c063ca3cd 100644 --- a/exasol/toolbox/sphinx/multiversion/main.py +++ b/exasol/toolbox/sphinx/multiversion/main.py @@ -21,11 +21,11 @@ from sphinx import config as sphinx_config from sphinx import project as sphinx_project -from exasol.toolbox.release import Version as ExasolVersion from exasol.toolbox.sphinx.multiversion import ( git, sphinx, ) +from exasol.toolbox.version import Version as ExasolVersion logging.basicConfig( level="INFO", diff --git a/exasol/toolbox/sphinx/multiversion/sphinx.py b/exasol/toolbox/sphinx/multiversion/sphinx.py index 18fb15488..a25c6c54c 100644 --- a/exasol/toolbox/sphinx/multiversion/sphinx.py +++ b/exasol/toolbox/sphinx/multiversion/sphinx.py @@ -9,8 +9,8 @@ from sphinx.locale import _ from sphinx.util import i18n as sphinx_i18n -from exasol.toolbox.release import Version as ExasolVersion from exasol.toolbox.version import VERSION as PLUGIN_VERSION +from exasol.toolbox.version import Version as ExasolVersion logger = logging.getLogger(__name__) diff --git a/exasol/toolbox/version/__init__.py b/exasol/toolbox/version/__init__.py new file mode 100644 index 000000000..d65968384 --- /dev/null +++ b/exasol/toolbox/version/__init__.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +import subprocess +from dataclasses import dataclass +from enum import Enum +from functools import ( + total_ordering, + wraps, +) +from pathlib import Path +from shutil import which +from typing import Any + +from exasol.toolbox.error import ToolboxError + + +def _index_or(container, index, default): + try: + return container[index] + except IndexError: + return default + + +class ReleaseTypes(Enum): + Major = "major" + Minor = "minor" + Patch = "patch" + + def __str__(self): + return self.name.lower() + + +def poetry_command(func): + @wraps(func) + def wrapper(*args, **kwargs): + cmd = which("poetry") + if not cmd: + raise ToolboxError("Couldn't find poetry executable") + try: + return func(*args, **kwargs) + except subprocess.CalledProcessError as ex: + raise ToolboxError(f"Failed to execute: {ex.cmd}") from ex + + return wrapper + + +@total_ordering +@dataclass(frozen=True) +class Version: + major: int + minor: int + patch: int + + def __str__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + def __lt__(self, other: object): + if not isinstance(other, Version): + return NotImplemented + return ( + self.major < other.major + or (self.major <= other.major and self.minor < other.minor) + or ( + self.major <= other.major + and self.minor <= other.minor + and self.patch < other.patch + ) + ) + + def __eq__(self, other: object): + if not isinstance(other, Version): + return NotImplemented + return ( + self.major == other.major + and self.minor == other.minor + and self.patch == other.patch + ) + + @staticmethod + def from_string(version): + parts = [int(number, base=0) for number in version.split(".")] + if len(parts) > 3: + raise ValueError( + "Version has an invalid format, " + f"expected: '..', actual: '{version}'" + ) + version = [_index_or(parts, i, 0) for i in range(3)] + return Version(*version) + + @staticmethod + @poetry_command + def from_poetry(): + output = subprocess.run( + ["poetry", "version", "--no-ansi", "--short"], + capture_output=True, + text=True, + ) + return Version.from_string(output.stdout.strip()) + + @staticmethod + @poetry_command + def upgrade_version_from_poetry(t: ReleaseTypes): + output = subprocess.run( + ["poetry", "version", str(t), "--dry-run", "--no-ansi", "--short"], + capture_output=True, + text=True, + ) + return Version.from_string(output.stdout.strip()) + + @staticmethod + def from_python_module(path: Path) -> Version: + """Retrieve version information from the `version` module""" + with open(path, encoding="utf-8") as file: + _locals: dict[str, Any] = {} + _globals: dict[str, Any] = {} + exec(file.read(), _locals, _globals) + + try: + version = _globals["VERSION"] + except KeyError as ex: + raise ToolboxError("Couldn't find version within module") from ex + + return Version.from_string(version) diff --git a/test/unit/cli_test.py b/test/unit/cli_test.py index 29ed8ab65..f836f6755 100644 --- a/test/unit/cli_test.py +++ b/test/unit/cli_test.py @@ -3,7 +3,7 @@ import pytest from exasol.toolbox.cli import version -from exasol.toolbox.release import Version +from exasol.toolbox.version import Version @pytest.mark.parametrize( diff --git a/test/unit/release_test.py b/test/unit/release_test.py index d1009fd42..987605c27 100644 --- a/test/unit/release_test.py +++ b/test/unit/release_test.py @@ -1,83 +1,15 @@ -import subprocess from datetime import datetime from inspect import cleandoc -from subprocess import CalledProcessError -from unittest.mock import ( - MagicMock, - patch, -) import pytest -from exasol.toolbox.error import ToolboxError -from exasol.toolbox.nox._release import ( - ReleaseError, - _trigger_release, -) from exasol.toolbox.release import ( - ReleaseTypes, - Version, extract_release_notes, new_changelog, - poetry_command, -) - - -@pytest.mark.parametrize( - "input,expected", - [ - ("1.2.3", Version(1, 2, 3)), - ("1.2", Version(1, 2, 0)), - ("1", Version(1, 0, 0)), - ], -) -def test_create_version_from_string(input, expected): - actual = Version.from_string(input) - assert expected == actual - - -@pytest.mark.parametrize( - "old_version,new_version,expected", - [ - (Version(1, 2, 3), Version(1, 2, 4), True), - (Version(1, 2, 3), Version(1, 3, 3), True), - (Version(1, 2, 3), Version(2, 2, 3), True), - (Version(1, 2, 3), Version(1, 1, 3), False), - (Version(1, 2, 3), Version(1, 2, 1), False), - (Version(1, 2, 3), Version(0, 3, 3), False), - ], ) -def test_is_later_version(old_version, new_version, expected): - actual = new_version > old_version - assert expected == actual - - -@pytest.fixture -def poetry_version(): - def set_poetry_version(version): - return subprocess.CompletedProcess( - args=["poetry", "version", "--no-ansi", "--short"], - returncode=0, - stdout=version, - stderr="", - ) - - yield set_poetry_version - - -@pytest.mark.parametrize( - "version,expected", - [ - ("1.2.3", Version(1, 2, 3)), - ("1.2", Version(1, 2, 0)), - ("1", Version(1, 0, 0)), - ], +from exasol.toolbox.version import ( + Version, ) -def test_version_from_poetry(poetry_version, version, expected): - with patch("subprocess.run", return_value=poetry_version(version)): - actual = Version.from_poetry() - - assert expected == actual @pytest.mark.parametrize( @@ -156,113 +88,3 @@ def test_extract_release_notes(unreleased_md): assert expected == actual -@pytest.fixture(scope="class") -def mock_from_poetry(): - with patch( - "exasol.toolbox.nox._release.Version.from_poetry", return_value="0.3.0" - ) as mock_obj: - yield mock_obj - - -class TestTriggerReleaseWithMocking: - @staticmethod - def _get_mock_string(args) -> str: - if args == ("git", "remote", "show", "origin"): - return "test\nHEAD branch: main\ntest" - if args in [("git", "tag", "--list"), ("gh", "release", "list")]: - return "0.1.0\n0.2.0" - return "" - - def _get_subprocess_run_mock(self, args) -> str: - return MagicMock(returncode=0, stdout=self._get_mock_string(args)) - - def test_works_as_expected(self, mock_from_poetry): - def simulate_pass(args, **kwargs): - return self._get_subprocess_run_mock(args) - - with patch("subprocess.run", side_effect=simulate_pass): - result = _trigger_release() - assert result == mock_from_poetry.return_value - - @pytest.mark.parametrize( - "error_cmd", - [ - ("git", "remote", "show", "origin"), - ("git", "checkout", "main"), - ("git", "pull"), - ("git", "tag", "--list"), - ("gh", "release", "list"), - ("git", "tag", "0.3.0"), - ("git", "push", "origin", "0.3.0"), - ], - ) - def test_caught_called_process_error_raises_release_error( - self, mock_from_poetry, error_cmd - ): - def simulate_fail(args, **kwargs): - if args == error_cmd: - raise CalledProcessError(returncode=1, cmd=error_cmd) - return self._get_subprocess_run_mock(args) - - with patch("subprocess.run", side_effect=simulate_fail): - with pytest.raises(ReleaseError) as ex: - _trigger_release() - assert str(error_cmd) in str(ex) - - def test_default_branch_could_not_be_found(self, mock_from_poetry): - def simulate_fail(args, **kwargs): - if args == ("git", "remote", "show", "origin"): - return MagicMock(returncode=0, stdout="DUMMY TEXT") - return self._get_subprocess_run_mock(args) - - with patch("subprocess.run", side_effect=simulate_fail): - with pytest.raises(ReleaseError) as ex: - _trigger_release() - assert "default branch could not be found" in str(ex) - - def test_tag_already_exists(self, mock_from_poetry): - version = mock_from_poetry.return_value - - def simulate_fail(args, **kwargs): - if args == ("git", "tag", "--list"): - return MagicMock(returncode=0, stdout=f"0.1.0\n0.2.0\n{version}") - return self._get_subprocess_run_mock(args) - - with patch("subprocess.run", side_effect=simulate_fail): - with pytest.raises(ReleaseError) as ex: - _trigger_release() - assert f"tag {version} already exists" in str(ex) - - def test_release_already_exists(self, mock_from_poetry): - version = mock_from_poetry.return_value - - def simulate_fail(args, **kwargs): - if args == ("gh", "release", "list"): - return MagicMock(returncode=0, stdout=f"0.1.0\n0.2.0\n{version}") - return self._get_subprocess_run_mock(args) - - with patch("subprocess.run", side_effect=simulate_fail): - with pytest.raises(ReleaseError) as ex: - _trigger_release() - assert f"release {version} already exists" in str(ex) - - -@patch("exasol.toolbox.release.which", return_value=None) -def test_poetry_decorator_no_poetry_executable(mock): - @poetry_command - def test(): - pass - - with pytest.raises(ToolboxError): - test() - - -@patch("exasol.toolbox.release.which", return_value="test/path") -def test_poetry_decorator_subprocess(mock): - @poetry_command - def test(): - raise subprocess.CalledProcessError(returncode=1, cmd=["test"]) - pass - - with pytest.raises(ToolboxError): - test() diff --git a/test/unit/version_test.py b/test/unit/version_test.py new file mode 100644 index 000000000..1322e010f --- /dev/null +++ b/test/unit/version_test.py @@ -0,0 +1,178 @@ +import subprocess +from subprocess import CalledProcessError +from unittest.mock import patch, MagicMock + +import pytest + +from exasol.toolbox.error import ToolboxError +from exasol.toolbox.nox._release import _trigger_release, ReleaseError +from exasol.toolbox.version import Version, poetry_command + + +@pytest.mark.parametrize( + "input,expected", + [ + ("1.2.3", Version(1, 2, 3)), + ("1.2", Version(1, 2, 0)), + ("1", Version(1, 0, 0)), + ], +) +def test_create_version_from_string(input, expected): + actual = Version.from_string(input) + assert expected == actual + + +@pytest.mark.parametrize( + "old_version,new_version,expected", + [ + (Version(1, 2, 3), Version(1, 2, 4), True), + (Version(1, 2, 3), Version(1, 3, 3), True), + (Version(1, 2, 3), Version(2, 2, 3), True), + (Version(1, 2, 3), Version(1, 1, 3), False), + (Version(1, 2, 3), Version(1, 2, 1), False), + (Version(1, 2, 3), Version(0, 3, 3), False), + ], +) +def test_is_later_version(old_version, new_version, expected): + actual = new_version > old_version + assert expected == actual + + +@pytest.fixture +def poetry_version(): + def set_poetry_version(version): + return subprocess.CompletedProcess( + args=["poetry", "version", "--no-ansi", "--short"], + returncode=0, + stdout=version, + stderr="", + ) + + yield set_poetry_version + + +@pytest.mark.parametrize( + "version,expected", + [ + ("1.2.3", Version(1, 2, 3)), + ("1.2", Version(1, 2, 0)), + ("1", Version(1, 0, 0)), + ], +) +def test_version_from_poetry(poetry_version, version, expected): + with patch("subprocess.run", return_value=poetry_version(version)): + actual = Version.from_poetry() + + assert expected == actual + + +@pytest.fixture(scope="class") +def mock_from_poetry(): + with patch( + "exasol.toolbox.nox._release.Version.from_poetry", return_value="0.3.0" + ) as mock_obj: + yield mock_obj + + +class TestTriggerReleaseWithMocking: + @staticmethod + def _get_mock_string(args) -> str: + if args == ("git", "remote", "show", "origin"): + return "test\nHEAD branch: main\ntest" + if args in [("git", "tag", "--list"), ("gh", "release", "list")]: + return "0.1.0\n0.2.0" + return "" + + def _get_subprocess_run_mock(self, args) -> str: + return MagicMock(returncode=0, stdout=self._get_mock_string(args)) + + def test_works_as_expected(self, mock_from_poetry): + def simulate_pass(args, **kwargs): + return self._get_subprocess_run_mock(args) + + with patch("subprocess.run", side_effect=simulate_pass): + result = _trigger_release() + assert result == mock_from_poetry.return_value + + @pytest.mark.parametrize( + "error_cmd", + [ + ("git", "remote", "show", "origin"), + ("git", "checkout", "main"), + ("git", "pull"), + ("git", "tag", "--list"), + ("gh", "release", "list"), + ("git", "tag", "0.3.0"), + ("git", "push", "origin", "0.3.0"), + ], + ) + def test_caught_called_process_error_raises_release_error( + self, mock_from_poetry, error_cmd + ): + def simulate_fail(args, **kwargs): + if args == error_cmd: + raise CalledProcessError(returncode=1, cmd=error_cmd) + return self._get_subprocess_run_mock(args) + + with patch("subprocess.run", side_effect=simulate_fail): + with pytest.raises(ReleaseError) as ex: + _trigger_release() + assert str(error_cmd) in str(ex) + + def test_default_branch_could_not_be_found(self, mock_from_poetry): + def simulate_fail(args, **kwargs): + if args == ("git", "remote", "show", "origin"): + return MagicMock(returncode=0, stdout="DUMMY TEXT") + return self._get_subprocess_run_mock(args) + + with patch("subprocess.run", side_effect=simulate_fail): + with pytest.raises(ReleaseError) as ex: + _trigger_release() + assert "default branch could not be found" in str(ex) + + def test_tag_already_exists(self, mock_from_poetry): + version = mock_from_poetry.return_value + + def simulate_fail(args, **kwargs): + if args == ("git", "tag", "--list"): + return MagicMock(returncode=0, stdout=f"0.1.0\n0.2.0\n{version}") + return self._get_subprocess_run_mock(args) + + with patch("subprocess.run", side_effect=simulate_fail): + with pytest.raises(ReleaseError) as ex: + _trigger_release() + assert f"tag {version} already exists" in str(ex) + + def test_release_already_exists(self, mock_from_poetry): + version = mock_from_poetry.return_value + + def simulate_fail(args, **kwargs): + if args == ("gh", "release", "list"): + return MagicMock(returncode=0, stdout=f"0.1.0\n0.2.0\n{version}") + return self._get_subprocess_run_mock(args) + + with patch("subprocess.run", side_effect=simulate_fail): + with pytest.raises(ReleaseError) as ex: + _trigger_release() + assert f"release {version} already exists" in str(ex) + + +@patch("exasol.toolbox.release.which", return_value=None) +def test_poetry_decorator_no_poetry_executable(mock): + @poetry_command + def test(): + pass + + with pytest.raises(ToolboxError): + test() + + +@patch("exasol.toolbox.release.which", return_value="test/path") +def test_poetry_decorator_subprocess(mock): + @poetry_command + def test(): + raise subprocess.CalledProcessError(returncode=1, cmd=["test"]) + pass + + with pytest.raises(ToolboxError): + test() From 68ab86319d5cb03f361546300e883ddf20728aaf Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Wed, 30 Apr 2025 08:39:19 +0200 Subject: [PATCH 07/14] resolve conversation --- .../toolbox/sphinx/multiversion/__init__.py | 4 +-- exasol/toolbox/sphinx/multiversion/sphinx.py | 2 +- test/unit/release_test.py | 2 -- test/unit/version_test.py | 35 +++++++++++++++++-- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/exasol/toolbox/sphinx/multiversion/__init__.py b/exasol/toolbox/sphinx/multiversion/__init__.py index 683b6c769..6ce5fa434 100644 --- a/exasol/toolbox/sphinx/multiversion/__init__.py +++ b/exasol/toolbox/sphinx/multiversion/__init__.py @@ -17,9 +17,9 @@ from exasol.toolbox.sphinx.multiversion.main import main from exasol.toolbox.sphinx.multiversion.sphinx import setup -from exasol.toolbox.version import VERSION +from exasol.toolbox.version import Version -__version__ = VERSION +__version__ = Version __all__ = [ "setup", diff --git a/exasol/toolbox/sphinx/multiversion/sphinx.py b/exasol/toolbox/sphinx/multiversion/sphinx.py index a25c6c54c..f53b43a29 100644 --- a/exasol/toolbox/sphinx/multiversion/sphinx.py +++ b/exasol/toolbox/sphinx/multiversion/sphinx.py @@ -9,7 +9,7 @@ from sphinx.locale import _ from sphinx.util import i18n as sphinx_i18n -from exasol.toolbox.version import VERSION as PLUGIN_VERSION +from exasol.toolbox.version import Version as PLUGIN_VERSION from exasol.toolbox.version import Version as ExasolVersion logger = logging.getLogger(__name__) diff --git a/test/unit/release_test.py b/test/unit/release_test.py index 987605c27..4373b781a 100644 --- a/test/unit/release_test.py +++ b/test/unit/release_test.py @@ -86,5 +86,3 @@ def test_extract_release_notes(unreleased_md): ) actual = extract_release_notes(unreleased_md) assert expected == actual - - diff --git a/test/unit/version_test.py b/test/unit/version_test.py index 1322e010f..982f10c9c 100644 --- a/test/unit/version_test.py +++ b/test/unit/version_test.py @@ -1,12 +1,21 @@ import subprocess from subprocess import CalledProcessError -from unittest.mock import patch, MagicMock +from unittest.mock import ( + MagicMock, + patch, +) import pytest from exasol.toolbox.error import ToolboxError -from exasol.toolbox.nox._release import _trigger_release, ReleaseError -from exasol.toolbox.version import Version, poetry_command +from exasol.toolbox.nox._release import ( + ReleaseError, + _trigger_release, +) +from exasol.toolbox.version import ( + Version, + poetry_command, +) @pytest.mark.parametrize( @@ -176,3 +185,23 @@ def test(): with pytest.raises(ToolboxError): test() + + +def test_version_from_python_module(tmp_path): + tmp_file = tmp_path / "file" + file = """ +MAJOR = 1 +MINOR = 2 +PATCH = 3 +VERSION = f"{MAJOR}.{MINOR}.{PATCH}" +__version__ = VERSION + """ + tmp_file.write_text(file) + assert Version.from_python_module(tmp_file) == Version.from_string("1.2.3") + + +def test_version_from_python_no_module_error(tmp_path): + file_path = tmp_path / "file" + file_path.write_text("") + with pytest.raises(ToolboxError) as ex: + Version.from_python_module(file_path) From 2974ddb9814efd2f23b1d6a73d8bc7925ee492f0 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Wed, 30 Apr 2025 08:58:23 +0200 Subject: [PATCH 08/14] resolve conversation --- exasol/toolbox/sphinx/multiversion/sphinx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exasol/toolbox/sphinx/multiversion/sphinx.py b/exasol/toolbox/sphinx/multiversion/sphinx.py index f53b43a29..feef9cf5d 100644 --- a/exasol/toolbox/sphinx/multiversion/sphinx.py +++ b/exasol/toolbox/sphinx/multiversion/sphinx.py @@ -9,8 +9,8 @@ from sphinx.locale import _ from sphinx.util import i18n as sphinx_i18n -from exasol.toolbox.version import Version as PLUGIN_VERSION from exasol.toolbox.version import Version as ExasolVersion +from exasol.toolbox.version import Version as PLUGIN_VERSION logger = logging.getLogger(__name__) From 832e59ddfcebebaddc3353acd034766eeb8cc17f Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Wed, 30 Apr 2025 09:34:59 +0200 Subject: [PATCH 09/14] fix typing error --- exasol/toolbox/cli.py | 2 +- exasol/toolbox/nox/_package_version.py | 2 +- exasol/toolbox/nox/_release.py | 5 +---- exasol/toolbox/release/__init__.py | 2 +- exasol/toolbox/sphinx/multiversion/__init__.py | 2 +- exasol/toolbox/sphinx/multiversion/main.py | 2 +- exasol/toolbox/sphinx/multiversion/sphinx.py | 3 +-- exasol/toolbox/{version => util}/__init__.py | 5 +---- exasol/toolbox/{ => util}/version.py | 0 test/unit/cli_test.py | 2 +- test/unit/release_test.py | 4 +--- test/unit/version_test.py | 5 +---- 12 files changed, 11 insertions(+), 23 deletions(-) rename exasol/toolbox/{version => util}/__init__.py (98%) rename exasol/toolbox/{ => util}/version.py (100%) diff --git a/exasol/toolbox/cli.py b/exasol/toolbox/cli.py index f136520b1..96990f657 100644 --- a/exasol/toolbox/cli.py +++ b/exasol/toolbox/cli.py @@ -1,6 +1,6 @@ from argparse import ArgumentTypeError -from exasol.toolbox.version import Version +from exasol.toolbox.util import Version def version(arg: str) -> Version: diff --git a/exasol/toolbox/nox/_package_version.py b/exasol/toolbox/nox/_package_version.py index 33a3e0439..db9c8b578 100644 --- a/exasol/toolbox/nox/_package_version.py +++ b/exasol/toolbox/nox/_package_version.py @@ -11,7 +11,7 @@ from nox import Session from exasol.toolbox.error import ToolboxError -from exasol.toolbox.version import Version +from exasol.toolbox.util import Version _SUCCESS = 0 _FAILURE = 1 diff --git a/exasol/toolbox/nox/_release.py b/exasol/toolbox/nox/_release.py index 4dfe306f6..4c1650f11 100644 --- a/exasol/toolbox/nox/_release.py +++ b/exasol/toolbox/nox/_release.py @@ -19,10 +19,7 @@ new_changes, new_unreleased, ) -from exasol.toolbox.version import ( - ReleaseTypes, - Version, -) +from exasol.toolbox.util import ReleaseTypes, Version from noxconfig import PROJECT_CONFIG diff --git a/exasol/toolbox/release/__init__.py b/exasol/toolbox/release/__init__.py index 377f19d32..82fac5485 100644 --- a/exasol/toolbox/release/__init__.py +++ b/exasol/toolbox/release/__init__.py @@ -4,7 +4,7 @@ from inspect import cleandoc from pathlib import Path -from exasol.toolbox.version import Version +from exasol.toolbox.util import Version def extract_release_notes(file: str | Path) -> str: diff --git a/exasol/toolbox/sphinx/multiversion/__init__.py b/exasol/toolbox/sphinx/multiversion/__init__.py index 6ce5fa434..0675383fd 100644 --- a/exasol/toolbox/sphinx/multiversion/__init__.py +++ b/exasol/toolbox/sphinx/multiversion/__init__.py @@ -17,7 +17,7 @@ from exasol.toolbox.sphinx.multiversion.main import main from exasol.toolbox.sphinx.multiversion.sphinx import setup -from exasol.toolbox.version import Version +from exasol.toolbox.util import Version __version__ = Version diff --git a/exasol/toolbox/sphinx/multiversion/main.py b/exasol/toolbox/sphinx/multiversion/main.py index c063ca3cd..c29f2aacf 100644 --- a/exasol/toolbox/sphinx/multiversion/main.py +++ b/exasol/toolbox/sphinx/multiversion/main.py @@ -25,7 +25,7 @@ git, sphinx, ) -from exasol.toolbox.version import Version as ExasolVersion +from exasol.toolbox.util import Version as ExasolVersion logging.basicConfig( level="INFO", diff --git a/exasol/toolbox/sphinx/multiversion/sphinx.py b/exasol/toolbox/sphinx/multiversion/sphinx.py index feef9cf5d..53d3061b7 100644 --- a/exasol/toolbox/sphinx/multiversion/sphinx.py +++ b/exasol/toolbox/sphinx/multiversion/sphinx.py @@ -9,8 +9,7 @@ from sphinx.locale import _ from sphinx.util import i18n as sphinx_i18n -from exasol.toolbox.version import Version as ExasolVersion -from exasol.toolbox.version import Version as PLUGIN_VERSION +from exasol.toolbox.util import Version as ExasolVersion, Version as PLUGIN_VERSION logger = logging.getLogger(__name__) diff --git a/exasol/toolbox/version/__init__.py b/exasol/toolbox/util/__init__.py similarity index 98% rename from exasol/toolbox/version/__init__.py rename to exasol/toolbox/util/__init__.py index d65968384..2cb518c2b 100644 --- a/exasol/toolbox/version/__init__.py +++ b/exasol/toolbox/util/__init__.py @@ -3,10 +3,7 @@ import subprocess from dataclasses import dataclass from enum import Enum -from functools import ( - total_ordering, - wraps, -) +from functools import wraps, total_ordering from pathlib import Path from shutil import which from typing import Any diff --git a/exasol/toolbox/version.py b/exasol/toolbox/util/version.py similarity index 100% rename from exasol/toolbox/version.py rename to exasol/toolbox/util/version.py diff --git a/test/unit/cli_test.py b/test/unit/cli_test.py index f836f6755..7d315aa99 100644 --- a/test/unit/cli_test.py +++ b/test/unit/cli_test.py @@ -3,7 +3,7 @@ import pytest from exasol.toolbox.cli import version -from exasol.toolbox.version import Version +from exasol.toolbox.util import Version @pytest.mark.parametrize( diff --git a/test/unit/release_test.py b/test/unit/release_test.py index 4373b781a..75b3e0541 100644 --- a/test/unit/release_test.py +++ b/test/unit/release_test.py @@ -7,9 +7,7 @@ extract_release_notes, new_changelog, ) -from exasol.toolbox.version import ( - Version, -) +from exasol.toolbox.util import Version @pytest.mark.parametrize( diff --git a/test/unit/version_test.py b/test/unit/version_test.py index 982f10c9c..527cd84bb 100644 --- a/test/unit/version_test.py +++ b/test/unit/version_test.py @@ -12,10 +12,7 @@ ReleaseError, _trigger_release, ) -from exasol.toolbox.version import ( - Version, - poetry_command, -) +from exasol.toolbox.util import poetry_command, Version @pytest.mark.parametrize( From 4d33ae962000a8a6de68e87050ec534a53828870 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Wed, 30 Apr 2025 09:53:32 +0200 Subject: [PATCH 10/14] fix --- exasol/toolbox/cli.py | 2 +- exasol/toolbox/nox/_package_version.py | 2 +- exasol/toolbox/nox/_release.py | 5 +- exasol/toolbox/release/__init__.py | 2 +- .../toolbox/sphinx/multiversion/__init__.py | 4 +- exasol/toolbox/sphinx/multiversion/main.py | 2 +- exasol/toolbox/sphinx/multiversion/sphinx.py | 3 +- exasol/toolbox/util/__init__.py | 120 ----------------- exasol/toolbox/version/__init__.py | 123 ++++++++++++++++++ noxconfig.py | 4 +- test/unit/cli_test.py | 2 +- test/unit/release_test.py | 2 +- test/unit/version_test.py | 5 +- 13 files changed, 144 insertions(+), 132 deletions(-) create mode 100644 exasol/toolbox/version/__init__.py diff --git a/exasol/toolbox/cli.py b/exasol/toolbox/cli.py index 96990f657..f136520b1 100644 --- a/exasol/toolbox/cli.py +++ b/exasol/toolbox/cli.py @@ -1,6 +1,6 @@ from argparse import ArgumentTypeError -from exasol.toolbox.util import Version +from exasol.toolbox.version import Version def version(arg: str) -> Version: diff --git a/exasol/toolbox/nox/_package_version.py b/exasol/toolbox/nox/_package_version.py index db9c8b578..33a3e0439 100644 --- a/exasol/toolbox/nox/_package_version.py +++ b/exasol/toolbox/nox/_package_version.py @@ -11,7 +11,7 @@ from nox import Session from exasol.toolbox.error import ToolboxError -from exasol.toolbox.util import Version +from exasol.toolbox.version import Version _SUCCESS = 0 _FAILURE = 1 diff --git a/exasol/toolbox/nox/_release.py b/exasol/toolbox/nox/_release.py index 4c1650f11..4dfe306f6 100644 --- a/exasol/toolbox/nox/_release.py +++ b/exasol/toolbox/nox/_release.py @@ -19,7 +19,10 @@ new_changes, new_unreleased, ) -from exasol.toolbox.util import ReleaseTypes, Version +from exasol.toolbox.version import ( + ReleaseTypes, + Version, +) from noxconfig import PROJECT_CONFIG diff --git a/exasol/toolbox/release/__init__.py b/exasol/toolbox/release/__init__.py index 82fac5485..377f19d32 100644 --- a/exasol/toolbox/release/__init__.py +++ b/exasol/toolbox/release/__init__.py @@ -4,7 +4,7 @@ from inspect import cleandoc from pathlib import Path -from exasol.toolbox.util import Version +from exasol.toolbox.version import Version def extract_release_notes(file: str | Path) -> str: diff --git a/exasol/toolbox/sphinx/multiversion/__init__.py b/exasol/toolbox/sphinx/multiversion/__init__.py index 0675383fd..baf0619e5 100644 --- a/exasol/toolbox/sphinx/multiversion/__init__.py +++ b/exasol/toolbox/sphinx/multiversion/__init__.py @@ -17,9 +17,9 @@ from exasol.toolbox.sphinx.multiversion.main import main from exasol.toolbox.sphinx.multiversion.sphinx import setup -from exasol.toolbox.util import Version +from exasol.toolbox.util.version import VERSION -__version__ = Version +__version__ = VERSION __all__ = [ "setup", diff --git a/exasol/toolbox/sphinx/multiversion/main.py b/exasol/toolbox/sphinx/multiversion/main.py index c29f2aacf..c063ca3cd 100644 --- a/exasol/toolbox/sphinx/multiversion/main.py +++ b/exasol/toolbox/sphinx/multiversion/main.py @@ -25,7 +25,7 @@ git, sphinx, ) -from exasol.toolbox.util import Version as ExasolVersion +from exasol.toolbox.version import Version as ExasolVersion logging.basicConfig( level="INFO", diff --git a/exasol/toolbox/sphinx/multiversion/sphinx.py b/exasol/toolbox/sphinx/multiversion/sphinx.py index 53d3061b7..899e52619 100644 --- a/exasol/toolbox/sphinx/multiversion/sphinx.py +++ b/exasol/toolbox/sphinx/multiversion/sphinx.py @@ -9,7 +9,8 @@ from sphinx.locale import _ from sphinx.util import i18n as sphinx_i18n -from exasol.toolbox.util import Version as ExasolVersion, Version as PLUGIN_VERSION +from exasol.toolbox.util.version import VERSION as PLUGIN_VERSION +from exasol.toolbox.version import Version as ExasolVersion logger = logging.getLogger(__name__) diff --git a/exasol/toolbox/util/__init__.py b/exasol/toolbox/util/__init__.py index 2cb518c2b..e69de29bb 100644 --- a/exasol/toolbox/util/__init__.py +++ b/exasol/toolbox/util/__init__.py @@ -1,120 +0,0 @@ -from __future__ import annotations - -import subprocess -from dataclasses import dataclass -from enum import Enum -from functools import wraps, total_ordering -from pathlib import Path -from shutil import which -from typing import Any - -from exasol.toolbox.error import ToolboxError - - -def _index_or(container, index, default): - try: - return container[index] - except IndexError: - return default - - -class ReleaseTypes(Enum): - Major = "major" - Minor = "minor" - Patch = "patch" - - def __str__(self): - return self.name.lower() - - -def poetry_command(func): - @wraps(func) - def wrapper(*args, **kwargs): - cmd = which("poetry") - if not cmd: - raise ToolboxError("Couldn't find poetry executable") - try: - return func(*args, **kwargs) - except subprocess.CalledProcessError as ex: - raise ToolboxError(f"Failed to execute: {ex.cmd}") from ex - - return wrapper - - -@total_ordering -@dataclass(frozen=True) -class Version: - major: int - minor: int - patch: int - - def __str__(self): - return f"{self.major}.{self.minor}.{self.patch}" - - def __lt__(self, other: object): - if not isinstance(other, Version): - return NotImplemented - return ( - self.major < other.major - or (self.major <= other.major and self.minor < other.minor) - or ( - self.major <= other.major - and self.minor <= other.minor - and self.patch < other.patch - ) - ) - - def __eq__(self, other: object): - if not isinstance(other, Version): - return NotImplemented - return ( - self.major == other.major - and self.minor == other.minor - and self.patch == other.patch - ) - - @staticmethod - def from_string(version): - parts = [int(number, base=0) for number in version.split(".")] - if len(parts) > 3: - raise ValueError( - "Version has an invalid format, " - f"expected: '..', actual: '{version}'" - ) - version = [_index_or(parts, i, 0) for i in range(3)] - return Version(*version) - - @staticmethod - @poetry_command - def from_poetry(): - output = subprocess.run( - ["poetry", "version", "--no-ansi", "--short"], - capture_output=True, - text=True, - ) - return Version.from_string(output.stdout.strip()) - - @staticmethod - @poetry_command - def upgrade_version_from_poetry(t: ReleaseTypes): - output = subprocess.run( - ["poetry", "version", str(t), "--dry-run", "--no-ansi", "--short"], - capture_output=True, - text=True, - ) - return Version.from_string(output.stdout.strip()) - - @staticmethod - def from_python_module(path: Path) -> Version: - """Retrieve version information from the `version` module""" - with open(path, encoding="utf-8") as file: - _locals: dict[str, Any] = {} - _globals: dict[str, Any] = {} - exec(file.read(), _locals, _globals) - - try: - version = _globals["VERSION"] - except KeyError as ex: - raise ToolboxError("Couldn't find version within module") from ex - - return Version.from_string(version) diff --git a/exasol/toolbox/version/__init__.py b/exasol/toolbox/version/__init__.py new file mode 100644 index 000000000..d65968384 --- /dev/null +++ b/exasol/toolbox/version/__init__.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +import subprocess +from dataclasses import dataclass +from enum import Enum +from functools import ( + total_ordering, + wraps, +) +from pathlib import Path +from shutil import which +from typing import Any + +from exasol.toolbox.error import ToolboxError + + +def _index_or(container, index, default): + try: + return container[index] + except IndexError: + return default + + +class ReleaseTypes(Enum): + Major = "major" + Minor = "minor" + Patch = "patch" + + def __str__(self): + return self.name.lower() + + +def poetry_command(func): + @wraps(func) + def wrapper(*args, **kwargs): + cmd = which("poetry") + if not cmd: + raise ToolboxError("Couldn't find poetry executable") + try: + return func(*args, **kwargs) + except subprocess.CalledProcessError as ex: + raise ToolboxError(f"Failed to execute: {ex.cmd}") from ex + + return wrapper + + +@total_ordering +@dataclass(frozen=True) +class Version: + major: int + minor: int + patch: int + + def __str__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + def __lt__(self, other: object): + if not isinstance(other, Version): + return NotImplemented + return ( + self.major < other.major + or (self.major <= other.major and self.minor < other.minor) + or ( + self.major <= other.major + and self.minor <= other.minor + and self.patch < other.patch + ) + ) + + def __eq__(self, other: object): + if not isinstance(other, Version): + return NotImplemented + return ( + self.major == other.major + and self.minor == other.minor + and self.patch == other.patch + ) + + @staticmethod + def from_string(version): + parts = [int(number, base=0) for number in version.split(".")] + if len(parts) > 3: + raise ValueError( + "Version has an invalid format, " + f"expected: '..', actual: '{version}'" + ) + version = [_index_or(parts, i, 0) for i in range(3)] + return Version(*version) + + @staticmethod + @poetry_command + def from_poetry(): + output = subprocess.run( + ["poetry", "version", "--no-ansi", "--short"], + capture_output=True, + text=True, + ) + return Version.from_string(output.stdout.strip()) + + @staticmethod + @poetry_command + def upgrade_version_from_poetry(t: ReleaseTypes): + output = subprocess.run( + ["poetry", "version", str(t), "--dry-run", "--no-ansi", "--short"], + capture_output=True, + text=True, + ) + return Version.from_string(output.stdout.strip()) + + @staticmethod + def from_python_module(path: Path) -> Version: + """Retrieve version information from the `version` module""" + with open(path, encoding="utf-8") as file: + _locals: dict[str, Any] = {} + _globals: dict[str, Any] = {} + exec(file.read(), _locals, _globals) + + try: + version = _globals["VERSION"] + except KeyError as ex: + raise ToolboxError("Couldn't find version within module") from ex + + return Version.from_string(version) diff --git a/noxconfig.py b/noxconfig.py index 50188226b..068be10d9 100644 --- a/noxconfig.py +++ b/noxconfig.py @@ -44,7 +44,9 @@ class Config: root: Path = Path(__file__).parent doc: Path = Path(__file__).parent / "doc" importlinter: Path = Path(__file__).parent / ".import_linter_config" - version_file: Path = Path(__file__).parent / "exasol" / "toolbox" / "version.py" + version_file: Path = ( + Path(__file__).parent / "exasol" / "toolbox" / "util" / "version.py" + ) path_filters: Iterable[str] = ( "dist", ".eggs", diff --git a/test/unit/cli_test.py b/test/unit/cli_test.py index 7d315aa99..f836f6755 100644 --- a/test/unit/cli_test.py +++ b/test/unit/cli_test.py @@ -3,7 +3,7 @@ import pytest from exasol.toolbox.cli import version -from exasol.toolbox.util import Version +from exasol.toolbox.version import Version @pytest.mark.parametrize( diff --git a/test/unit/release_test.py b/test/unit/release_test.py index 75b3e0541..28bd91d1f 100644 --- a/test/unit/release_test.py +++ b/test/unit/release_test.py @@ -7,7 +7,7 @@ extract_release_notes, new_changelog, ) -from exasol.toolbox.util import Version +from exasol.toolbox.version import Version @pytest.mark.parametrize( diff --git a/test/unit/version_test.py b/test/unit/version_test.py index 527cd84bb..982f10c9c 100644 --- a/test/unit/version_test.py +++ b/test/unit/version_test.py @@ -12,7 +12,10 @@ ReleaseError, _trigger_release, ) -from exasol.toolbox.util import poetry_command, Version +from exasol.toolbox.version import ( + Version, + poetry_command, +) @pytest.mark.parametrize( From eecf9fe1814de4b0861e7a10ff08f11adf2a4688 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Wed, 30 Apr 2025 10:03:12 +0200 Subject: [PATCH 11/14] fix --- test/unit/version_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/version_test.py b/test/unit/version_test.py index 982f10c9c..a709fec39 100644 --- a/test/unit/version_test.py +++ b/test/unit/version_test.py @@ -166,7 +166,7 @@ def simulate_fail(args, **kwargs): assert f"release {version} already exists" in str(ex) -@patch("exasol.toolbox.release.which", return_value=None) +@patch("exasol.toolbox.version.which", return_value=None) def test_poetry_decorator_no_poetry_executable(mock): @poetry_command def test(): @@ -176,7 +176,7 @@ def test(): test() -@patch("exasol.toolbox.release.which", return_value="test/path") +@patch("exasol.toolbox.version.which", return_value="test/path") def test_poetry_decorator_subprocess(mock): @poetry_command def test(): From 5fc4cc2afc79eac1ad8eb130e4dba138acf3ff3d Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Wed, 30 Apr 2025 11:25:44 +0200 Subject: [PATCH 12/14] fix --- exasol/toolbox/cli.py | 2 +- exasol/toolbox/nox/_package_version.py | 2 +- exasol/toolbox/nox/_release.py | 5 +- exasol/toolbox/release/__init__.py | 2 +- .../toolbox/sphinx/multiversion/__init__.py | 2 +- exasol/toolbox/sphinx/multiversion/main.py | 2 +- exasol/toolbox/sphinx/multiversion/sphinx.py | 4 +- exasol/toolbox/util/version.py | 131 ++++++++++++++++-- exasol/toolbox/version.py | 11 ++ test/unit/cli_test.py | 2 +- test/unit/release_test.py | 2 +- test/unit/version_test.py | 9 +- 12 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 exasol/toolbox/version.py diff --git a/exasol/toolbox/cli.py b/exasol/toolbox/cli.py index f136520b1..377572ee4 100644 --- a/exasol/toolbox/cli.py +++ b/exasol/toolbox/cli.py @@ -1,6 +1,6 @@ from argparse import ArgumentTypeError -from exasol.toolbox.version import Version +from exasol.toolbox.util.version import Version def version(arg: str) -> Version: diff --git a/exasol/toolbox/nox/_package_version.py b/exasol/toolbox/nox/_package_version.py index 33a3e0439..1435f249b 100644 --- a/exasol/toolbox/nox/_package_version.py +++ b/exasol/toolbox/nox/_package_version.py @@ -11,7 +11,7 @@ from nox import Session from exasol.toolbox.error import ToolboxError -from exasol.toolbox.version import Version +from exasol.toolbox.util.version import Version _SUCCESS = 0 _FAILURE = 1 diff --git a/exasol/toolbox/nox/_release.py b/exasol/toolbox/nox/_release.py index 4dfe306f6..3a8079890 100644 --- a/exasol/toolbox/nox/_release.py +++ b/exasol/toolbox/nox/_release.py @@ -19,10 +19,7 @@ new_changes, new_unreleased, ) -from exasol.toolbox.version import ( - ReleaseTypes, - Version, -) +from exasol.toolbox.util.version import ReleaseTypes, Version from noxconfig import PROJECT_CONFIG diff --git a/exasol/toolbox/release/__init__.py b/exasol/toolbox/release/__init__.py index 377f19d32..a20e24d0d 100644 --- a/exasol/toolbox/release/__init__.py +++ b/exasol/toolbox/release/__init__.py @@ -4,7 +4,7 @@ from inspect import cleandoc from pathlib import Path -from exasol.toolbox.version import Version +from exasol.toolbox.util.version import Version def extract_release_notes(file: str | Path) -> str: diff --git a/exasol/toolbox/sphinx/multiversion/__init__.py b/exasol/toolbox/sphinx/multiversion/__init__.py index baf0619e5..683b6c769 100644 --- a/exasol/toolbox/sphinx/multiversion/__init__.py +++ b/exasol/toolbox/sphinx/multiversion/__init__.py @@ -17,7 +17,7 @@ from exasol.toolbox.sphinx.multiversion.main import main from exasol.toolbox.sphinx.multiversion.sphinx import setup -from exasol.toolbox.util.version import VERSION +from exasol.toolbox.version import VERSION __version__ = VERSION diff --git a/exasol/toolbox/sphinx/multiversion/main.py b/exasol/toolbox/sphinx/multiversion/main.py index c063ca3cd..f183d97cc 100644 --- a/exasol/toolbox/sphinx/multiversion/main.py +++ b/exasol/toolbox/sphinx/multiversion/main.py @@ -25,7 +25,7 @@ git, sphinx, ) -from exasol.toolbox.version import Version as ExasolVersion +from exasol.toolbox.util.version import Version as ExasolVersion logging.basicConfig( level="INFO", diff --git a/exasol/toolbox/sphinx/multiversion/sphinx.py b/exasol/toolbox/sphinx/multiversion/sphinx.py index 899e52619..3bb038d7a 100644 --- a/exasol/toolbox/sphinx/multiversion/sphinx.py +++ b/exasol/toolbox/sphinx/multiversion/sphinx.py @@ -9,8 +9,8 @@ from sphinx.locale import _ from sphinx.util import i18n as sphinx_i18n -from exasol.toolbox.util.version import VERSION as PLUGIN_VERSION -from exasol.toolbox.version import Version as ExasolVersion +from exasol.toolbox.version import VERSION as PLUGIN_VERSION +from exasol.toolbox.util.version import Version as ExasolVersion logger = logging.getLogger(__name__) diff --git a/exasol/toolbox/util/version.py b/exasol/toolbox/util/version.py index 2423b5120..2cb518c2b 100644 --- a/exasol/toolbox/util/version.py +++ b/exasol/toolbox/util/version.py @@ -1,11 +1,120 @@ -# ATTENTION: -# This file is generated by exasol/toolbox/nox/_package_version.py when using: -# * either "poetry run -- nox -s project:fix" -# * or "poetry run -- nox version:check -- --fix" -# Do not edit this file manually! -# If you need to change the version, do so in the pyproject.toml, e.g. by using `poetry version X.Y.Z`. -MAJOR = 1 -MINOR = 0 -PATCH = 1 -VERSION = f"{MAJOR}.{MINOR}.{PATCH}" -__version__ = VERSION +from __future__ import annotations + +import subprocess +from dataclasses import dataclass +from enum import Enum +from functools import wraps, total_ordering +from pathlib import Path +from shutil import which +from typing import Any + +from exasol.toolbox.error import ToolboxError + + +def _index_or(container, index, default): + try: + return container[index] + except IndexError: + return default + + +class ReleaseTypes(Enum): + Major = "major" + Minor = "minor" + Patch = "patch" + + def __str__(self): + return self.name.lower() + + +def poetry_command(func): + @wraps(func) + def wrapper(*args, **kwargs): + cmd = which("poetry") + if not cmd: + raise ToolboxError("Couldn't find poetry executable") + try: + return func(*args, **kwargs) + except subprocess.CalledProcessError as ex: + raise ToolboxError(f"Failed to execute: {ex.cmd}") from ex + + return wrapper + + +@total_ordering +@dataclass(frozen=True) +class Version: + major: int + minor: int + patch: int + + def __str__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + def __lt__(self, other: object): + if not isinstance(other, Version): + return NotImplemented + return ( + self.major < other.major + or (self.major <= other.major and self.minor < other.minor) + or ( + self.major <= other.major + and self.minor <= other.minor + and self.patch < other.patch + ) + ) + + def __eq__(self, other: object): + if not isinstance(other, Version): + return NotImplemented + return ( + self.major == other.major + and self.minor == other.minor + and self.patch == other.patch + ) + + @staticmethod + def from_string(version): + parts = [int(number, base=0) for number in version.split(".")] + if len(parts) > 3: + raise ValueError( + "Version has an invalid format, " + f"expected: '..', actual: '{version}'" + ) + version = [_index_or(parts, i, 0) for i in range(3)] + return Version(*version) + + @staticmethod + @poetry_command + def from_poetry(): + output = subprocess.run( + ["poetry", "version", "--no-ansi", "--short"], + capture_output=True, + text=True, + ) + return Version.from_string(output.stdout.strip()) + + @staticmethod + @poetry_command + def upgrade_version_from_poetry(t: ReleaseTypes): + output = subprocess.run( + ["poetry", "version", str(t), "--dry-run", "--no-ansi", "--short"], + capture_output=True, + text=True, + ) + return Version.from_string(output.stdout.strip()) + + @staticmethod + def from_python_module(path: Path) -> Version: + """Retrieve version information from the `version` module""" + with open(path, encoding="utf-8") as file: + _locals: dict[str, Any] = {} + _globals: dict[str, Any] = {} + exec(file.read(), _locals, _globals) + + try: + version = _globals["VERSION"] + except KeyError as ex: + raise ToolboxError("Couldn't find version within module") from ex + + return Version.from_string(version) diff --git a/exasol/toolbox/version.py b/exasol/toolbox/version.py new file mode 100644 index 000000000..2423b5120 --- /dev/null +++ b/exasol/toolbox/version.py @@ -0,0 +1,11 @@ +# ATTENTION: +# This file is generated by exasol/toolbox/nox/_package_version.py when using: +# * either "poetry run -- nox -s project:fix" +# * or "poetry run -- nox version:check -- --fix" +# Do not edit this file manually! +# If you need to change the version, do so in the pyproject.toml, e.g. by using `poetry version X.Y.Z`. +MAJOR = 1 +MINOR = 0 +PATCH = 1 +VERSION = f"{MAJOR}.{MINOR}.{PATCH}" +__version__ = VERSION diff --git a/test/unit/cli_test.py b/test/unit/cli_test.py index f836f6755..e97c36544 100644 --- a/test/unit/cli_test.py +++ b/test/unit/cli_test.py @@ -3,7 +3,7 @@ import pytest from exasol.toolbox.cli import version -from exasol.toolbox.version import Version +from exasol.toolbox.util.version import Version @pytest.mark.parametrize( diff --git a/test/unit/release_test.py b/test/unit/release_test.py index 28bd91d1f..3dca1cbc9 100644 --- a/test/unit/release_test.py +++ b/test/unit/release_test.py @@ -7,7 +7,7 @@ extract_release_notes, new_changelog, ) -from exasol.toolbox.version import Version +from exasol.toolbox.util.version import Version @pytest.mark.parametrize( diff --git a/test/unit/version_test.py b/test/unit/version_test.py index a709fec39..ca65be433 100644 --- a/test/unit/version_test.py +++ b/test/unit/version_test.py @@ -12,10 +12,7 @@ ReleaseError, _trigger_release, ) -from exasol.toolbox.version import ( - Version, - poetry_command, -) +from exasol.toolbox.util.version import poetry_command, Version @pytest.mark.parametrize( @@ -166,7 +163,7 @@ def simulate_fail(args, **kwargs): assert f"release {version} already exists" in str(ex) -@patch("exasol.toolbox.version.which", return_value=None) +@patch("exasol.toolbox.util.version.which", return_value=None) def test_poetry_decorator_no_poetry_executable(mock): @poetry_command def test(): @@ -176,7 +173,7 @@ def test(): test() -@patch("exasol.toolbox.version.which", return_value="test/path") +@patch("exasol.toolbox.version.util.which", return_value="test/path") def test_poetry_decorator_subprocess(mock): @poetry_command def test(): From 533e9b288ab284f61ff0db2cae2d28d73287f982 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Wed, 30 Apr 2025 12:43:30 +0200 Subject: [PATCH 13/14] fix --- exasol/toolbox/nox/_release.py | 5 +- exasol/toolbox/sphinx/multiversion/sphinx.py | 2 +- exasol/toolbox/util/version.py | 5 +- exasol/toolbox/version/__init__.py | 123 ------------------- noxconfig.py | 4 +- test/unit/version_test.py | 5 +- 6 files changed, 14 insertions(+), 130 deletions(-) delete mode 100644 exasol/toolbox/version/__init__.py diff --git a/exasol/toolbox/nox/_release.py b/exasol/toolbox/nox/_release.py index 3a8079890..fcd7ebe1b 100644 --- a/exasol/toolbox/nox/_release.py +++ b/exasol/toolbox/nox/_release.py @@ -19,7 +19,10 @@ new_changes, new_unreleased, ) -from exasol.toolbox.util.version import ReleaseTypes, Version +from exasol.toolbox.util.version import ( + ReleaseTypes, + Version, +) from noxconfig import PROJECT_CONFIG diff --git a/exasol/toolbox/sphinx/multiversion/sphinx.py b/exasol/toolbox/sphinx/multiversion/sphinx.py index 3bb038d7a..91db2b881 100644 --- a/exasol/toolbox/sphinx/multiversion/sphinx.py +++ b/exasol/toolbox/sphinx/multiversion/sphinx.py @@ -9,8 +9,8 @@ from sphinx.locale import _ from sphinx.util import i18n as sphinx_i18n -from exasol.toolbox.version import VERSION as PLUGIN_VERSION from exasol.toolbox.util.version import Version as ExasolVersion +from exasol.toolbox.version import VERSION as PLUGIN_VERSION logger = logging.getLogger(__name__) diff --git a/exasol/toolbox/util/version.py b/exasol/toolbox/util/version.py index 2cb518c2b..d65968384 100644 --- a/exasol/toolbox/util/version.py +++ b/exasol/toolbox/util/version.py @@ -3,7 +3,10 @@ import subprocess from dataclasses import dataclass from enum import Enum -from functools import wraps, total_ordering +from functools import ( + total_ordering, + wraps, +) from pathlib import Path from shutil import which from typing import Any diff --git a/exasol/toolbox/version/__init__.py b/exasol/toolbox/version/__init__.py deleted file mode 100644 index d65968384..000000000 --- a/exasol/toolbox/version/__init__.py +++ /dev/null @@ -1,123 +0,0 @@ -from __future__ import annotations - -import subprocess -from dataclasses import dataclass -from enum import Enum -from functools import ( - total_ordering, - wraps, -) -from pathlib import Path -from shutil import which -from typing import Any - -from exasol.toolbox.error import ToolboxError - - -def _index_or(container, index, default): - try: - return container[index] - except IndexError: - return default - - -class ReleaseTypes(Enum): - Major = "major" - Minor = "minor" - Patch = "patch" - - def __str__(self): - return self.name.lower() - - -def poetry_command(func): - @wraps(func) - def wrapper(*args, **kwargs): - cmd = which("poetry") - if not cmd: - raise ToolboxError("Couldn't find poetry executable") - try: - return func(*args, **kwargs) - except subprocess.CalledProcessError as ex: - raise ToolboxError(f"Failed to execute: {ex.cmd}") from ex - - return wrapper - - -@total_ordering -@dataclass(frozen=True) -class Version: - major: int - minor: int - patch: int - - def __str__(self): - return f"{self.major}.{self.minor}.{self.patch}" - - def __lt__(self, other: object): - if not isinstance(other, Version): - return NotImplemented - return ( - self.major < other.major - or (self.major <= other.major and self.minor < other.minor) - or ( - self.major <= other.major - and self.minor <= other.minor - and self.patch < other.patch - ) - ) - - def __eq__(self, other: object): - if not isinstance(other, Version): - return NotImplemented - return ( - self.major == other.major - and self.minor == other.minor - and self.patch == other.patch - ) - - @staticmethod - def from_string(version): - parts = [int(number, base=0) for number in version.split(".")] - if len(parts) > 3: - raise ValueError( - "Version has an invalid format, " - f"expected: '..', actual: '{version}'" - ) - version = [_index_or(parts, i, 0) for i in range(3)] - return Version(*version) - - @staticmethod - @poetry_command - def from_poetry(): - output = subprocess.run( - ["poetry", "version", "--no-ansi", "--short"], - capture_output=True, - text=True, - ) - return Version.from_string(output.stdout.strip()) - - @staticmethod - @poetry_command - def upgrade_version_from_poetry(t: ReleaseTypes): - output = subprocess.run( - ["poetry", "version", str(t), "--dry-run", "--no-ansi", "--short"], - capture_output=True, - text=True, - ) - return Version.from_string(output.stdout.strip()) - - @staticmethod - def from_python_module(path: Path) -> Version: - """Retrieve version information from the `version` module""" - with open(path, encoding="utf-8") as file: - _locals: dict[str, Any] = {} - _globals: dict[str, Any] = {} - exec(file.read(), _locals, _globals) - - try: - version = _globals["VERSION"] - except KeyError as ex: - raise ToolboxError("Couldn't find version within module") from ex - - return Version.from_string(version) diff --git a/noxconfig.py b/noxconfig.py index 068be10d9..50188226b 100644 --- a/noxconfig.py +++ b/noxconfig.py @@ -44,9 +44,7 @@ class Config: root: Path = Path(__file__).parent doc: Path = Path(__file__).parent / "doc" importlinter: Path = Path(__file__).parent / ".import_linter_config" - version_file: Path = ( - Path(__file__).parent / "exasol" / "toolbox" / "util" / "version.py" - ) + version_file: Path = Path(__file__).parent / "exasol" / "toolbox" / "version.py" path_filters: Iterable[str] = ( "dist", ".eggs", diff --git a/test/unit/version_test.py b/test/unit/version_test.py index ca65be433..94104419e 100644 --- a/test/unit/version_test.py +++ b/test/unit/version_test.py @@ -12,7 +12,10 @@ ReleaseError, _trigger_release, ) -from exasol.toolbox.util.version import poetry_command, Version +from exasol.toolbox.util.version import ( + Version, + poetry_command, +) @pytest.mark.parametrize( From 39956f9df193f02b6eb9d38fdf733164137e6365 Mon Sep 17 00:00:00 2001 From: Jannis Mittenzwei Date: Wed, 30 Apr 2025 12:48:47 +0200 Subject: [PATCH 14/14] fix --- test/unit/version_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/version_test.py b/test/unit/version_test.py index 94104419e..88e714472 100644 --- a/test/unit/version_test.py +++ b/test/unit/version_test.py @@ -176,7 +176,7 @@ def test(): test() -@patch("exasol.toolbox.version.util.which", return_value="test/path") +@patch("exasol.toolbox.util.version.which", return_value="test/path") def test_poetry_decorator_subprocess(mock): @poetry_command def test():