diff --git a/src/poetry/core/version/pep440/version.py b/src/poetry/core/version/pep440/version.py index c1252a6f4..eeae009a8 100644 --- a/src/poetry/core/version/pep440/version.py +++ b/src/poetry/core/version/pep440/version.py @@ -2,6 +2,7 @@ import dataclasses import functools +import warnings from typing import TYPE_CHECKING from typing import Any @@ -201,31 +202,48 @@ def is_unstable(self) -> bool: def is_stable(self) -> bool: return not self.is_unstable() + def _is_increment_required(self) -> bool: + return self.is_stable() or (not self.is_prerelease() and self.is_postrelease()) + def next_major(self: T) -> T: release = self.release - if self.is_stable() or Release(self.release.major, 0, 0) < self.release: - release = self.release.next_major() + if self._is_increment_required() or Release(release.major, 0, 0) < release: + release = release.next_major() return self.__class__(epoch=self.epoch, release=release) def next_minor(self: T) -> T: release = self.release if ( - self.is_stable() - or Release(self.release.major, self.release.minor, 0) < self.release + self._is_increment_required() + or Release(release.major, release.minor, 0) < release ): - release = self.release.next_minor() + release = release.next_minor() return self.__class__(epoch=self.epoch, release=release) def next_patch(self: T) -> T: - return self.__class__( - epoch=self.epoch, - release=self.release.next_patch() if self.is_stable() else self.release, - ) + release = self.release + if ( + self._is_increment_required() + or Release(release.major, release.minor, release.patch) < release + ): + release = release.next_patch() + return self.__class__(epoch=self.epoch, release=release) def next_prerelease(self: T, next_phase: bool = False) -> PEP440Version: + if self.is_stable(): + warnings.warn( + "Calling next_prerelease() on a stable release is deprecated for its" + " ambiguity. Use next_major(), next_minor(), etc. together with" + " first_prerelease()", + DeprecationWarning, + stacklevel=2, + ) if self.is_prerelease(): assert self.pre is not None - pre = self.pre.next_phase() if next_phase else self.pre.next() + if not self.is_devrelease() or self.is_postrelease(): + pre = self.pre.next_phase() if next_phase else self.pre.next() + else: + pre = self.pre else: pre = ReleaseTag(RELEASE_PHASE_ID_ALPHA) return self.__class__(epoch=self.epoch, release=self.release, pre=pre) @@ -233,14 +251,13 @@ def next_prerelease(self: T, next_phase: bool = False) -> PEP440Version: def next_postrelease(self: T) -> T: if self.is_postrelease(): assert self.post is not None - post = self.post.next() + post = self.post.next() if self.dev is None else self.post else: post = ReleaseTag(RELEASE_PHASE_ID_POST) return self.__class__( epoch=self.epoch, release=self.release, pre=self.pre, - dev=self.dev, post=post, ) @@ -249,6 +266,13 @@ def next_devrelease(self: T) -> T: assert self.dev is not None dev = self.dev.next() else: + warnings.warn( + "Calling next_devrelease() on a non dev release is deprecated for its" + " ambiguity. Use next_major(), next_minor(), etc. together with" + " first_devrelease()", + DeprecationWarning, + stacklevel=2, + ) dev = ReleaseTag(RELEASE_PHASE_ID_DEV) return self.__class__( epoch=self.epoch, @@ -265,6 +289,15 @@ def first_prerelease(self: T) -> T: pre=ReleaseTag(RELEASE_PHASE_ID_ALPHA), ) + def first_devrelease(self: T) -> T: + return self.__class__( + epoch=self.epoch, + release=self.release, + pre=self.pre, + post=self.post, + dev=ReleaseTag(RELEASE_PHASE_ID_DEV), + ) + def replace(self: T, **kwargs: Any) -> T: return self.__class__( **{ diff --git a/tests/semver/test_version.py b/tests/semver/test_version.py index 67c029ad6..755354c99 100644 --- a/tests/semver/test_version.py +++ b/tests/semver/test_version.py @@ -293,11 +293,147 @@ def test_difference() -> None: @pytest.mark.parametrize( "version, expected", [ - ("1.2.3", "1.2.3-dev.0"), - ("1.2.3-alpha.0", "1.2.3-alpha.0-dev.0"), - ("1.2.3-dev.0", "1.2.3-dev.1"), + ("1", "2"), + ("2!1", "2!2"), + ("1+local", "2"), + ("1.2", "2.0"), + ("1.2.3", "2.0.0"), + ("1.2.3.4", "2.0.0.0"), + ("1.dev0", "1"), + ("1.2.dev0", "2.0"), + ("1.post1", "2"), + ("1.2.post1", "2.0"), + ("1.post1.dev0", "2"), + ("1.2.post1.dev0", "2.0"), + ("1.a1", "1"), + ("1.2a1", "2.0"), + ("1.a1.post2", "1"), + ("1.2a1.post2", "2.0"), + ("1.a1.post2.dev0", "1"), + ("1.2a1.post2.dev0", "2.0"), ], ) -def test_next_devrelease(version: str, expected: str) -> None: +def test_next_major(version: str, expected: str) -> None: v = Version.parse(version) - assert v.next_devrelease() == Version.parse(expected) + assert str(v.next_major()) == expected + + +@pytest.mark.parametrize( + "version, expected", + [ + ("1", "1.1"), + ("1.2", "1.3"), + ("2!1.2", "2!1.3"), + ("1.2+local", "1.3"), + ("1.2.3", "1.3.0"), + ("1.2.3.4", "1.3.0.0"), + ("1.dev0", "1"), + ("1.2dev0", "1.2"), + ("1.2.3dev0", "1.3.0"), + ("1.post1", "1.1"), + ("1.2.post1", "1.3"), + ("1.2.3.post1", "1.3.0"), + ("1.post1.dev0", "1.1"), + ("1.2.post1.dev0", "1.3"), + ("1.a1", "1"), + ("1.2a1", "1.2"), + ("1.2.3a1", "1.3.0"), + ("1.a1.post2", "1"), + ("1.2a1.post2", "1.2"), + ("1.2.3a1.post2", "1.3.0"), + ("1.a1.post2.dev0", "1"), + ("1.2a1.post2.dev0", "1.2"), + ("1.2.3a1.post2.dev0", "1.3.0"), + ], +) +def test_next_minor(version: str, expected: str) -> None: + v = Version.parse(version) + assert str(v.next_minor()) == expected + + +@pytest.mark.parametrize( + "version, expected", + [ + ("1", "1.0.1"), + ("1.2", "1.2.1"), + ("1.2.3", "1.2.4"), + ("2!1.2.3", "2!1.2.4"), + ("1.2.3+local", "1.2.4"), + ("1.2.3.4", "1.2.4.0"), + ("1.dev0", "1"), + ("1.2dev0", "1.2"), + ("1.2.3dev0", "1.2.3"), + ("1.2.3.4dev0", "1.2.4.0"), + ("1.post1", "1.0.1"), + ("1.2.post1", "1.2.1"), + ("1.2.3.post1", "1.2.4"), + ("1.post1.dev0", "1.0.1"), + ("1.2.post1.dev0", "1.2.1"), + ("1.2.3.post1.dev0", "1.2.4"), + ("1.a1", "1"), + ("1.2a1", "1.2"), + ("1.2.3a1", "1.2.3"), + ("1.2.3.4a1", "1.2.4.0"), + ("1.a1.post2", "1"), + ("1.2a1.post2", "1.2"), + ("1.2.3a1.post2", "1.2.3"), + ("1.2.3.4a1.post2", "1.2.4.0"), + ("1.a1.post2.dev0", "1"), + ("1.2a1.post2.dev0", "1.2"), + ("1.2.3a1.post2.dev0", "1.2.3"), + ("1.2.3.4a1.post2.dev0", "1.2.4.0"), + ], +) +def test_next_patch(version: str, expected: str) -> None: + v = Version.parse(version) + assert str(v.next_patch()) == expected + + +@pytest.mark.parametrize( + "version, expected", + [ + ("1.2a1", "1.2a2"), + ("2!1.2a1", "2!1.2a2"), + ("1.2dev0", "1.2a0"), + ("1.2a1.dev0", "1.2a1"), + ("1.2a1.post1.dev0", "1.2a2"), + ], +) +def test_next_prerelease(version: str, expected: str) -> None: + v = Version.parse(version) + assert str(v.next_prerelease()) == expected + + +@pytest.mark.parametrize( + "version, expected", + [ + ("1", "1.post0"), + ("1.post1", "1.post2"), + ("9!1.2.3.4", "9!1.2.3.4.post0"), + ("9!1.2.3.4.post2", "9!1.2.3.4.post3"), + ("1.dev0", "1.post0"), + ("1.post1.dev0", "1.post1"), + ("1a1", "1a1.post0"), + ("1a1.dev0", "1a1.post0"), + ("1a1.post2", "1a1.post3"), + ("1a1.post2.dev0", "1a1.post2"), + ], +) +def test_next_postrelease(version: str, expected: str) -> None: + v = Version.parse(version) + assert str(v.next_postrelease()) == expected + + +def test_next_devrelease() -> None: + v = Version.parse("9!1.2.3a1.post2.dev3") + assert str(v.next_devrelease()) == "9!1.2.3a1.post2.dev4" + + +def test_next_firstprerelease() -> None: + v = Version.parse("9!1.2.3a1.post2.dev3") + assert str(v.first_prerelease()) == "9!1.2.3a0" + + +def test_next_firstdevrelease() -> None: + v = Version.parse("9!1.2.3a1.post2.dev3") + assert str(v.first_devrelease()) == "9!1.2.3a1.post2.dev0"