From 70530ee3195cc48b7b295518dbd8547e9b3175b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Mar 2023 11:13:24 +0100 Subject: [PATCH] Change: Introduce a Version class and merge VersionCalculator Move VersionCalculator into version module because with the introduction of the Version class the code in the version module depends on each other. The new Version class will abstract the versions and used instead of strings. --- pontos/version/calculator.py | 224 ------------------ pontos/version/version.py | 187 ++++++++++++++- .../{test_calculator.py => test_version.py} | 115 ++++----- 3 files changed, 245 insertions(+), 281 deletions(-) delete mode 100644 pontos/version/calculator.py rename tests/version/{test_calculator.py => test_version.py} (71%) diff --git a/pontos/version/calculator.py b/pontos/version/calculator.py deleted file mode 100644 index 7b075914..00000000 --- a/pontos/version/calculator.py +++ /dev/null @@ -1,224 +0,0 @@ -# Copyright (C) 2023 Greenbone Networks GmbH -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from datetime import datetime - -from packaging.version import InvalidVersion, Version - -from .errors import VersionError - - -class VersionCalculator: - def next_patch_version(self, version: str) -> str: - """ - Get the next patch version from a valid version - - Examples: - "1.2.3" will return "1.2.4" - "1.2.3.dev1" will return "1.2.3" - - Raises: - VersionError if version is invalid. - """ - - try: - current_version = Version(version) - - if current_version.is_prerelease: - next_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro}" - ) - else: - next_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro + 1}" - ) - - return str(next_version) - except InvalidVersion as e: - raise VersionError(e) from None - - def next_calendar_version(self, version: str) -> str: - """ - Find the correct next calendar version by checking latest version and - the today's date - - Raises: - VersionError if version is invalid. - """ - - current_version = Version(version) - - today = datetime.today() - current_year_short = today.year % 100 - - if current_version.major < current_year_short or ( - current_version.major == current_year_short - and current_version.minor < today.month - ): - release_version = Version(f"{current_year_short}.{today.month}.0") - return str(release_version) - - if ( - current_version.major == today.year % 100 - and current_version.minor == today.month - ): - if current_version.dev is None: - release_version = Version( - f"{current_year_short}.{today.month}." - f"{current_version.micro + 1}" - ) - else: - release_version = Version( - f"{current_year_short}.{today.month}." - f"{current_version.micro}" - ) - return str(release_version) - else: - raise VersionError( - f"'{current_version}' is higher than " - f"'{current_year_short}.{today.month}'." - ) - - def next_minor_version(self, version: str) -> str: - try: - current_version = Version(version) - release_version = Version( - f"{current_version.major}.{current_version.minor +1}.0" - ) - return str(release_version) - except InvalidVersion as e: - raise VersionError(e) from None - - def next_major_version(self, version: str) -> str: - try: - current_version = Version(version) - release_version = Version(f"{current_version.major + 1}.0.0") - return str(release_version) - except InvalidVersion as e: - raise VersionError(e) from None - - def next_dev_version(self, version: str) -> str: - try: - current_version = Version(version) - if current_version.is_devrelease: - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro }.dev{current_version.dev + 1}" - ) - else: - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro + 1 }.dev1" - ) - - return str(release_version) - except InvalidVersion as e: - raise VersionError(e) from None - - def next_alpha_version(self, version: str) -> str: - try: - current_version = Version(version) - if current_version.is_devrelease: - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro }a1" - ) - elif ( - current_version.is_prerelease and current_version.pre[0] == "a" - ): - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro }a{current_version.pre[1] + 1}" - ) - else: - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro + 1 }a1" - ) - - return str(release_version) - except InvalidVersion as e: - raise VersionError(e) from None - - def next_beta_version(self, version: str) -> str: - try: - current_version = Version(version) - if current_version.is_devrelease or ( - current_version.is_prerelease and current_version.pre[0] == "a" - ): - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro }b1" - ) - elif ( - current_version.is_prerelease and current_version.pre[0] == "b" - ): - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro }b{current_version.pre[1] + 1}" - ) - else: - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro + 1 }b1" - ) - - return str(release_version) - except InvalidVersion as e: - raise VersionError(e) from None - - def next_release_candidate_version(self, version: str) -> str: - try: - current_version = Version(version) - if current_version.is_devrelease or ( - current_version.is_prerelease and current_version.pre[0] != "rc" - ): - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro }rc1" - ) - elif ( - current_version.is_prerelease and current_version.pre[0] == "rc" - ): - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro }rc{current_version.pre[1] + 1}" - ) - else: - release_version = Version( - f"{current_version.major}." - f"{current_version.minor}." - f"{current_version.micro + 1 }rc1" - ) - - return str(release_version) - except InvalidVersion as e: - raise VersionError(e) from None diff --git a/pontos/version/version.py b/pontos/version/version.py index fb031d23..9e54f420 100644 --- a/pontos/version/version.py +++ b/pontos/version/version.py @@ -17,19 +17,200 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field +from datetime import datetime from pathlib import Path from typing import Type -from pontos.version.calculator import VersionCalculator +from packaging.version import InvalidVersion +from packaging.version import Version as PackagingVersion + +from .errors import VersionError + + +class Version(PackagingVersion): + """ + A class handling version information + """ + + +def parse_version(version: str) -> Version: + try: + return Version(version) + except InvalidVersion as e: + raise VersionError(e) from None @dataclass class VersionUpdate: - previous: str - new: str + previous: Version + new: Version changed_files: list[Path] = field(default_factory=list) +class VersionCalculator: + def next_patch_version(self, current_version: Version) -> Version: + """ + Get the next patch version from a valid version + + Examples: + "1.2.3" will return "1.2.4" + "1.2.3.dev1" will return "1.2.3" + + Raises: + VersionError if version is invalid. + """ + + if current_version.is_prerelease: + next_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro}" + ) + else: + next_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro + 1}" + ) + + return next_version + + def next_calendar_version(self, current_version: Version) -> Version: + """ + Find the correct next calendar version by checking latest version and + the today's date + + Raises: + VersionError if version is invalid. + """ + + today = datetime.today() + current_year_short = today.year % 100 + + if current_version.major < current_year_short or ( + current_version.major == current_year_short + and current_version.minor < today.month + ): + return parse_version(f"{current_year_short}.{today.month}.0") + + if ( + current_version.major == today.year % 100 + and current_version.minor == today.month + ): + if current_version.dev is None: + release_version = parse_version( + f"{current_year_short}.{today.month}." + f"{current_version.micro + 1}" + ) + else: + release_version = parse_version( + f"{current_year_short}.{today.month}." + f"{current_version.micro}" + ) + return release_version + else: + raise VersionError( + f"'{current_version}' is higher than " + f"'{current_year_short}.{today.month}'." + ) + + def next_minor_version(self, current_version: Version) -> Version: + return parse_version( + f"{current_version.major}.{current_version.minor +1}.0" + ) + + def next_major_version(self, current_version: Version) -> Version: + return parse_version(f"{current_version.major + 1}.0.0") + + def next_dev_version(self, current_version: Version) -> Version: + if current_version.is_devrelease: + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro }.dev{current_version.dev + 1}" + ) + else: + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro + 1 }.dev1" + ) + + return release_version + + def next_alpha_version(self, current_version: Version) -> Version: + if current_version.is_devrelease: + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro }a1" + ) + elif current_version.is_prerelease and current_version.pre[0] == "a": + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro }a{current_version.pre[1] + 1}" + ) + else: + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro + 1 }a1" + ) + + return release_version + + def next_beta_version(self, current_version: Version) -> Version: + if current_version.is_devrelease or ( + current_version.is_prerelease and current_version.pre[0] == "a" + ): + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro }b1" + ) + elif current_version.is_prerelease and current_version.pre[0] == "b": + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro }b{current_version.pre[1] + 1}" + ) + else: + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro + 1 }b1" + ) + + return release_version + + def next_release_candidate_version( + self, current_version: Version + ) -> Version: + if current_version.is_devrelease or ( + current_version.is_prerelease and current_version.pre[0] != "rc" + ): + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro }rc1" + ) + elif current_version.is_prerelease and current_version.pre[0] == "rc": + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro }rc{current_version.pre[1] + 1}" + ) + else: + release_version = parse_version( + f"{current_version.major}." + f"{current_version.minor}." + f"{current_version.micro + 1 }rc1" + ) + + return release_version + + class VersionCommand(ABC): """Generic class usable to implement the version commands for several programming languages""" diff --git a/tests/version/test_calculator.py b/tests/version/test_version.py similarity index 71% rename from tests/version/test_calculator.py rename to tests/version/test_version.py index 766cb530..f4c6c62c 100644 --- a/tests/version/test_calculator.py +++ b/tests/version/test_version.py @@ -18,8 +18,8 @@ import unittest from datetime import datetime -from pontos.version.calculator import VersionCalculator from pontos.version.errors import VersionError +from pontos.version.version import Version, VersionCalculator, parse_version class VersionCalculatorTestCase(unittest.TestCase): @@ -52,15 +52,11 @@ def test_next_patch_version(self): for current_version, assert_version in zip( current_versions, assert_versions ): - release_version = calculator.next_patch_version(current_version) - - self.assertEqual(release_version, assert_version) - - def test_next_patch_version_invalid_version(self): - calculator = VersionCalculator() + release_version = calculator.next_patch_version( + Version(current_version) + ) - with self.assertRaisesRegex(VersionError, "Invalid version: 'abc'"): - calculator.next_patch_version("abc") + self.assertEqual(release_version, Version(assert_version)) def test_next_calendar_versions(self): calculator = VersionCalculator() @@ -83,8 +79,10 @@ def test_next_calendar_versions(self): for current_version, assert_version in zip( current_versions, assert_versions ): - release_version = calculator.next_calendar_version(current_version) - self.assertEqual(release_version, assert_version) + release_version = calculator.next_calendar_version( + Version(current_version) + ) + self.assertEqual(release_version, Version(assert_version)) def test_next_calendar_version_error(self): calculator = VersionCalculator() @@ -92,11 +90,11 @@ def test_next_calendar_version_error(self): year_short = today.year % 100 with self.assertRaisesRegex(VersionError, "'.+' is higher than '.+'."): - calculator.next_calendar_version(f"{year_short + 1}.1.0") + calculator.next_calendar_version(Version(f"{year_short + 1}.1.0")) with self.assertRaisesRegex(VersionError, "'.+' is higher than '.+'."): calculator.next_calendar_version( - f"{year_short}.{today.month + 1}.0" + Version(f"{year_short}.{today.month + 1}.0") ) def test_next_minor_version(self): @@ -128,13 +126,10 @@ def test_next_minor_version(self): for current_version, assert_version in zip( current_versions, assert_versions ): - release_version = calculator.next_minor_version(current_version) - self.assertEqual(release_version, assert_version) - - def test_next_minor_version_error(self): - calculator = VersionCalculator() - with self.assertRaisesRegex(VersionError, "Invalid version: 'abc'"): - calculator.next_minor_version("abc") + release_version = calculator.next_minor_version( + Version(current_version) + ) + self.assertEqual(release_version, Version(assert_version)) def test_next_major_version(self): calculator = VersionCalculator() @@ -165,13 +160,10 @@ def test_next_major_version(self): for current_version, assert_version in zip( current_versions, assert_versions ): - release_version = calculator.next_major_version(current_version) - self.assertEqual(release_version, assert_version) - - def test_next_major_version_error(self): - calculator = VersionCalculator() - with self.assertRaisesRegex(VersionError, "Invalid version: 'abc'"): - calculator.next_major_version("abc") + release_version = calculator.next_major_version( + Version(current_version) + ) + self.assertEqual(release_version, Version(assert_version)) def test_next_alpha_version(self): calculator = VersionCalculator() @@ -202,13 +194,10 @@ def test_next_alpha_version(self): for current_version, assert_version in zip( current_versions, assert_versions ): - release_version = calculator.next_alpha_version(current_version) - self.assertEqual(release_version, assert_version) - - def test_next_alpha_version_error(self): - calculator = VersionCalculator() - with self.assertRaisesRegex(VersionError, "Invalid version: 'abc'"): - calculator.next_alpha_version("abc") + release_version = calculator.next_alpha_version( + Version(current_version) + ) + self.assertEqual(release_version, Version(assert_version)) def test_next_beta_version(self): calculator = VersionCalculator() @@ -239,13 +228,10 @@ def test_next_beta_version(self): for current_version, assert_version in zip( current_versions, assert_versions ): - release_version = calculator.next_beta_version(current_version) - self.assertEqual(release_version, assert_version) - - def test_next_beta_version_error(self): - calculator = VersionCalculator() - with self.assertRaisesRegex(VersionError, "Invalid version: 'abc'"): - calculator.next_beta_version("abc") + release_version = calculator.next_beta_version( + Version(current_version) + ) + self.assertEqual(release_version, Version(assert_version)) def test_next_release_candidate_version(self): calculator = VersionCalculator() @@ -277,14 +263,9 @@ def test_next_release_candidate_version(self): current_versions, assert_versions ): release_version = calculator.next_release_candidate_version( - current_version + Version(current_version) ) - self.assertEqual(release_version, assert_version) - - def test_next_release_candidate_version_error(self): - calculator = VersionCalculator() - with self.assertRaisesRegex(VersionError, "Invalid version: 'abc'"): - calculator.next_release_candidate_version("abc") + self.assertEqual(release_version, Version(assert_version)) def test_next_dev_version(self): calculator = VersionCalculator() @@ -315,10 +296,36 @@ def test_next_dev_version(self): for current_version, assert_version in zip( current_versions, assert_versions ): - release_version = calculator.next_dev_version(current_version) - self.assertEqual(release_version, assert_version) + release_version = calculator.next_dev_version( + Version(current_version) + ) + self.assertEqual(release_version, Version(assert_version)) - def test_next_dev_version_error(self): - calculator = VersionCalculator() - with self.assertRaisesRegex(VersionError, "Invalid version: 'abc'"): - calculator.next_dev_version("abc") + +class ParseVersionTestCase(unittest.TestCase): + def test_parse_version(self): + versions = [ + "0.0.1", + "1.2.3", + "1.2.3.post1", + "1.2.3a1", + "1.2.3b1", + "1.2.3rc1", + "22.4.1", + "22.4.1.dev1", + "22.4.1.dev3", + ] + for version in versions: + self.assertEqual(parse_version(version), Version(version)) + + def test_parse_error(self): + versions = [ + "abc", + "1.2.3d", + ] + + for version in versions: + with self.assertRaisesRegex( + VersionError, "^Invalid version: '.*'$" + ): + parse_version(version)