diff --git a/pontos/version/calculator.py b/pontos/version/calculator.py deleted file mode 100644 index 7b075914f..000000000 --- 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 fb031d23f..9e54f4200 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 766cb530d..f4c6c62c7 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)