Skip to content

Commit 15075ce

Browse files
bjoernricksy0urself
authored andcommitted
Add: Implement a VersioningScheme based on PEP 440
Reimplement the initial versioning behavior by adding a VersioningScheme based on PEP 440.
1 parent 743ad38 commit 15075ce

File tree

1 file changed

+305
-0
lines changed

1 file changed

+305
-0
lines changed

pontos/version/scheme/_pep440.py

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# Copyright (C) 2023 Greenbone Networks GmbH
2+
#
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
from typing import Any, Optional, Tuple
19+
20+
from packaging.version import InvalidVersion
21+
from packaging.version import Version as PackagingVersion
22+
23+
from pontos.version.calculator import VersionCalculator
24+
25+
from ..errors import VersionError
26+
from ..version import Version
27+
from ._scheme import VersioningScheme
28+
29+
__all__ = (
30+
"PEP440Version",
31+
"PEP440VersionCalculator",
32+
"PEP440VersioningScheme",
33+
)
34+
35+
36+
class PEP440Version(Version):
37+
"""
38+
A class handling PEP 440 based version information
39+
"""
40+
41+
def __init__(self, version: str) -> None:
42+
self._version = PackagingVersion(version)
43+
44+
@property
45+
def major(self) -> int:
46+
"""The first item of the version"""
47+
return self._version.major
48+
49+
@property
50+
def minor(self) -> int:
51+
"""The second item of :attr:`release` or ``0`` if unavailable."""
52+
return self._version.minor
53+
54+
@property
55+
def patch(self) -> int:
56+
"""The third item of :attr:`release` or ``0`` if unavailable."""
57+
return self._version.micro
58+
59+
@property
60+
def pre(self) -> Optional[Tuple[str, int]]:
61+
"""The pre-release segment of the version."""
62+
if self.is_dev_release:
63+
return ("dev", self._version.dev)
64+
return self._version.pre
65+
66+
@property
67+
def dev(self) -> Optional[int]:
68+
"""The development number of the version."""
69+
return self._version.dev
70+
71+
@property
72+
def local(self) -> Optional[str]:
73+
"""The local version segment of the version."""
74+
return self._version.local
75+
76+
@property
77+
def is_pre_release(self) -> bool:
78+
"""Whether this version is a pre-release."""
79+
return self._version.is_prerelease
80+
81+
@property
82+
def is_dev_release(self) -> bool:
83+
"""Whether this version is a development release."""
84+
return self._version.is_devrelease
85+
86+
@property
87+
def is_alpha_release(self) -> bool:
88+
"""Whether this version is a alpha release."""
89+
return self.pre is not None and self.pre[0] == "alpha"
90+
91+
@property
92+
def is_beta_release(self) -> bool:
93+
"""Whether this version is a beta release."""
94+
return self.pre is not None and self.pre[0] == "beta"
95+
96+
@property
97+
def is_release_candidate(self) -> bool:
98+
"""Whether this version is a release candidate."""
99+
return self.pre is not None and self.pre[0] == "rc"
100+
101+
@classmethod
102+
def from_string(cls, version: str) -> "Version":
103+
"""
104+
Create a version from a version string
105+
106+
Args:
107+
version: Version string to parse
108+
109+
Raises:
110+
VersionError: If the version string is invalid.
111+
112+
Returns:
113+
A new version instance
114+
"""
115+
try:
116+
return cls(version)
117+
except InvalidVersion as e:
118+
raise VersionError(e) from None
119+
120+
@classmethod
121+
def from_version(cls, version: "Version") -> "PEP440Version":
122+
"""
123+
Convert a version (if necessary)
124+
125+
This method can be used to convert version instances from different
126+
versioning schemes.
127+
"""
128+
129+
if isinstance(version, cls):
130+
return version
131+
132+
if version.is_dev_release:
133+
return cls.from_string(
134+
f"{version.major}."
135+
f"{version.minor}."
136+
f"{version.patch}"
137+
f".dev{version.dev}"
138+
f"{'+' + version.local if version.local else ''}"
139+
)
140+
if version.is_pre_release:
141+
return cls.from_string(
142+
f"{version.major}."
143+
f"{version.minor}."
144+
f"{version.patch}"
145+
f"-{version.pre[0]}{version.pre[1]}"
146+
f"{'+' + version.local if version.local else ''}"
147+
)
148+
149+
return cls.from_string(str(version))
150+
151+
def __eq__(self, other: Any) -> bool:
152+
if not isinstance(other, Version):
153+
raise ValueError(f"Can't compare {type(self)} with {type(other)}")
154+
if not isinstance(other, type(self)):
155+
other = self.from_version(other)
156+
return self._version == other._version
157+
158+
def __ne__(self, other: Any) -> bool:
159+
if not isinstance(other, Version):
160+
raise ValueError(f"Can't compare {type(self)} with {type(other)}")
161+
if not isinstance(other, type(self)):
162+
other = self.from_version(other)
163+
return self._version != other._version
164+
165+
def __str__(self) -> str:
166+
return str(self._version)
167+
168+
169+
class PEP440VersionCalculator(VersionCalculator):
170+
version_cls = PEP440Version
171+
172+
"""
173+
A PEP 440 version calculator
174+
"""
175+
176+
@classmethod
177+
def next_dev_version(cls, current_version: Version) -> Version:
178+
"""
179+
Get the next development version from a valid version
180+
181+
Examples:
182+
"1.2.3" will return "1.2.4.dev1"
183+
"1.2.3.dev1" will return "1.2.3.dev2"
184+
"""
185+
if current_version.is_dev_release:
186+
return cls.version_from_string(
187+
f"{current_version.major}."
188+
f"{current_version.minor}."
189+
f"{current_version.patch}.dev{current_version.dev + 1}"
190+
)
191+
192+
if current_version.is_pre_release:
193+
return cls.version_from_string(
194+
f"{current_version.major}."
195+
f"{current_version.minor}."
196+
f"{current_version.patch}"
197+
f"{current_version.pre[0]}{current_version.pre[1]}+dev1"
198+
)
199+
200+
return cls.version_from_string(
201+
f"{current_version.major}."
202+
f"{current_version.minor}."
203+
f"{current_version.micro + 1 }.dev1"
204+
)
205+
206+
@classmethod
207+
def next_alpha_version(cls, current_version: Version) -> Version:
208+
"""
209+
Get the next alpha version from a valid version
210+
211+
Examples:
212+
"1.2.3" will return "1.2.4a1"
213+
"1.2.3.dev1" will return "1.2.3a1"
214+
"""
215+
if current_version.is_dev_release:
216+
return cls.version_from_string(
217+
f"{current_version.major}."
218+
f"{current_version.minor}."
219+
f"{current_version.patch}a1"
220+
)
221+
222+
if current_version.is_alpha_release:
223+
return cls.version_from_string(
224+
f"{current_version.major}."
225+
f"{current_version.minor}."
226+
f"{current_version.patch}a{current_version.pre[1] + 1}"
227+
)
228+
229+
return cls.version_from_string(
230+
f"{current_version.major}."
231+
f"{current_version.minor}."
232+
f"{current_version.patch + 1}a1"
233+
)
234+
235+
@classmethod
236+
def next_beta_version(cls, current_version: Version) -> Version:
237+
"""
238+
Get the next beta version from a valid version
239+
240+
Examples:
241+
"1.2.3" will return "1.2.4b1"
242+
"1.2.3.dev1" will return "1.2.3b1"
243+
"""
244+
if current_version.is_dev_release or current_version.is_alpha_release:
245+
return cls.version_from_string(
246+
f"{current_version.major}."
247+
f"{current_version.minor}."
248+
f"{current_version.patch}b1"
249+
)
250+
251+
if current_version.is_beta_release:
252+
return cls.version_from_string(
253+
f"{current_version.major}."
254+
f"{current_version.minor}."
255+
f"{current_version.patch}b{current_version.pre[1] + 1}"
256+
)
257+
return cls.version_from_string(
258+
f"{current_version.major}."
259+
f"{current_version.minor}."
260+
f"{current_version.patch + 1}b1"
261+
)
262+
263+
@classmethod
264+
def next_release_candidate_version(
265+
cls, current_version: Version
266+
) -> Version:
267+
"""
268+
Get the next alpha version from a valid version
269+
270+
Examples:
271+
"1.2.3" will return "1.2.4rc1"
272+
"1.2.3.dev1" will return "1.2.3rc1"
273+
"""
274+
if (
275+
current_version.is_dev_release
276+
or current_version.is_alpha_release
277+
or current_version.is_beta_release
278+
):
279+
return cls.version_from_string(
280+
f"{current_version.major}."
281+
f"{current_version.minor}."
282+
f"{current_version.patch}rc1"
283+
)
284+
285+
if current_version.is_release_candidate:
286+
return cls.version_from_string(
287+
f"{current_version.major}."
288+
f"{current_version.minor}."
289+
f"{current_version.patch}rc{current_version.pre[1] + 1}"
290+
)
291+
292+
return cls.version_from_string(
293+
f"{current_version.major}."
294+
f"{current_version.minor}."
295+
f"{current_version.patch + 1}rc1"
296+
)
297+
298+
299+
class PEP440VersioningScheme(VersioningScheme):
300+
"""
301+
PEP 440 versioning scheme
302+
"""
303+
304+
version_calculator_cls = PEP440VersionCalculator
305+
version_cls = PEP440Version

0 commit comments

Comments
 (0)