Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 22c5ad2

Browse filesBrowse files
author
Bas Roos
committed
Fix #460 Raising a prerelease version always results in a newer version, and raising an empty prerelease version has the option to raise the patch version as well
1 parent 6adf876 commit 22c5ad2
Copy full SHA for 22c5ad2

File tree

6 files changed

+122
-26
lines changed
Filter options

6 files changed

+122
-26
lines changed

‎changelog.d/460.bugfix.rst

Copy file name to clipboard
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
:meth:`~semver.version.Version.bump_prerelease` will now add `.0` to an
2+
existing prerelease when the last segment of the current prerelease, split by
3+
dots (`.`), is not numeric. This is to ensure the new prerelease is considered
4+
higher than the previous one.
5+
6+
:meth:`~semver.version.Version.bump_prerelease` now also support an argument
7+
`bump_when_empty` which will bump the patch version if there is no existing
8+
prerelease, to ensure the resulting version is considered a higher version than
9+
the previous one.

‎docs/usage/raise-parts-of-a-version.rst

Copy file name to clipboardExpand all lines: docs/usage/raise-parts-of-a-version.rst
+15-3Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ Raising Parts of a Version
33

44
.. note::
55

6-
Keep in mind, "raising" the pre-release only will make your
7-
complete version *lower* than before.
6+
Keep in mind, by default, "raising" the pre-release for a version without an existing
7+
prerelease part, only will make your complete version *lower* than before.
88

99
For example, having version ``1.0.0`` and raising the pre-release
1010
will lead to ``1.0.0-rc.1``, but ``1.0.0-rc.1`` is smaller than ``1.0.0``.
1111

12-
If you search for a way to take into account this behavior, look for the
12+
You can work around this by supplying the `bump_when_empty=true` argument to the
13+
:meth:`~semver.version.Version.bump_prerelease` method, or by using the
1314
method :meth:`~semver.version.Version.next_version`
1415
in section :ref:`increase-parts-of-a-version`.
1516

@@ -67,4 +68,15 @@ is not taken into account:
6768
>>> str(Version.parse("3.4.5-rc.1").bump_prerelease(''))
6869
'3.4.5-rc.2'
6970
71+
If the last part of the existing prerelease, split by dots (`.`), is not numeric,
72+
we will add `.0` to ensure the new prerelease is higher than the previous one
73+
(otherwise, raising `rc9` to `rc10` would result in a lower version, as non-numeric
74+
parts are sorted alphabetically):
75+
76+
.. code-block:: python
77+
78+
>>> str(Version.parse("3.4.5-rc9").bump_prerelease())
79+
'3.4.5-rc9.0'
80+
>>> str(Version.parse("3.4.5-rc.9").bump_prerelease())
81+
'3.4.5-rc.10'
7082

‎src/semver/version.py

Copy file name to clipboardExpand all lines: src/semver/version.py
+48-15Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ class Version:
7777
#: The names of the different parts of a version
7878
NAMES: ClassVar[Tuple[str, ...]] = tuple([item[1:] for item in __slots__])
7979

80-
#: Regex for number in a prerelease
80+
#: Regex for number in a build
8181
_LAST_NUMBER: ClassVar[Pattern[str]] = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+")
82+
#: Regex for number in a prerelease
83+
_LAST_PRERELEASE: ClassVar[Pattern[str]] = re.compile(r"^(.*\.)?(\d+)$")
8284
#: Regex template for a semver version
8385
_REGEX_TEMPLATE: ClassVar[
8486
str
@@ -245,6 +247,24 @@ def __iter__(self) -> VersionIterator:
245247
"""Return iter(self)."""
246248
yield from self.to_tuple()
247249

250+
@staticmethod
251+
def _increment_prerelease(string: str) -> str:
252+
"""
253+
Check if the last part of a dot-separated string is numeric. If yes,
254+
increase them. Else, add '.0'
255+
256+
:param string: the prerelease version to increment
257+
:return: the incremented string
258+
259+
"""
260+
match = Version._LAST_PRERELEASE.search(string)
261+
if match:
262+
next_ = str(int(match.group(2)) + 1)
263+
string = match.group(1) + next_ if match.group(1) else next_
264+
else:
265+
string += ".0"
266+
return string
267+
248268
@staticmethod
249269
def _increment_string(string: str) -> str:
250270
"""
@@ -305,35 +325,50 @@ def bump_patch(self) -> "Version":
305325
cls = type(self)
306326
return cls(self._major, self._minor, self._patch + 1)
307327

308-
def bump_prerelease(self, token: Optional[str] = "rc") -> "Version":
328+
def bump_prerelease(
329+
self,
330+
token: Optional[str] = "rc",
331+
bump_when_empty: Optional[bool] = False
332+
) -> "Version":
309333
"""
310334
Raise the prerelease part of the version, return a new object but leave
311335
self untouched.
312336
337+
.. versionchanged:: VERSION
338+
Parameter `bump_when_empty` added. When set to true, bumps the patch version
339+
when called with a version that has no prerelease segment, so the return
340+
value will be considered a newer version.
341+
342+
Adds `.0` to the prerelease if the last part of the dot-separated
343+
prerelease is not a number.
344+
313345
:param token: defaults to ``'rc'``
314346
:return: new :class:`Version` object with the raised prerelease part.
315347
The original object is not modified.
316348
317349
>>> ver = semver.parse("3.4.5")
318350
>>> ver.bump_prerelease().prerelease
319-
'rc.2'
351+
'rc.1'
320352
>>> ver.bump_prerelease('').prerelease
321353
'1'
322354
>>> ver.bump_prerelease(None).prerelease
323355
'rc.1'
324356
"""
325357
cls = type(self)
358+
patch = self._patch
326359
if self._prerelease is not None:
327-
prerelease = self._prerelease
328-
elif token == "":
329-
prerelease = "0"
330-
elif token is None:
331-
prerelease = "rc.0"
360+
prerelease = cls._increment_prerelease(self._prerelease)
332361
else:
333-
prerelease = str(token) + ".0"
362+
if bump_when_empty:
363+
patch += 1
364+
if token == "":
365+
prerelease = "1"
366+
elif token is None:
367+
prerelease = "rc.1"
368+
else:
369+
prerelease = str(token) + ".1"
334370

335-
prerelease = cls._increment_string(prerelease)
336-
return cls(self._major, self._minor, self._patch, prerelease)
371+
return cls(self._major, self._minor, patch, prerelease)
337372

338373
def bump_build(self, token: Optional[str] = "build") -> "Version":
339374
"""
@@ -457,10 +492,8 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version":
457492
# Only check the main parts:
458493
if part in cls.NAMES[:3]:
459494
return getattr(version, "bump_" + part)()
460-
461-
if not version.prerelease:
462-
version = version.bump_patch()
463-
return version.bump_prerelease(prerelease_token)
495+
else:
496+
return version.bump_prerelease(prerelease_token, bump_when_empty=True)
464497

465498
@_comparator
466499
def __eq__(self, other: Comparable) -> bool: # type: ignore

‎tests/test_bump.py

Copy file name to clipboardExpand all lines: tests/test_bump.py
+48-7Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
bump_minor,
77
bump_patch,
88
bump_prerelease,
9+
compare,
910
parse_version_info,
1011
)
1112

@@ -32,81 +33,120 @@ def test_should_versioninfo_bump_minor_and_patch():
3233
v = parse_version_info("3.4.5")
3334
expected = parse_version_info("3.5.1")
3435
assert v.bump_minor().bump_patch() == expected
36+
assert v.compare(expected) == -1
3537

3638

3739
def test_should_versioninfo_bump_patch_and_prerelease():
3840
v = parse_version_info("3.4.5-rc.1")
3941
expected = parse_version_info("3.4.6-rc.1")
4042
assert v.bump_patch().bump_prerelease() == expected
43+
assert v.compare(expected) == -1
4144

4245

4346
def test_should_versioninfo_bump_patch_and_prerelease_with_token():
4447
v = parse_version_info("3.4.5-dev.1")
4548
expected = parse_version_info("3.4.6-dev.1")
4649
assert v.bump_patch().bump_prerelease("dev") == expected
50+
assert v.compare(expected) == -1
4751

4852

4953
def test_should_versioninfo_bump_prerelease_and_build():
5054
v = parse_version_info("3.4.5-rc.1+build.1")
5155
expected = parse_version_info("3.4.5-rc.2+build.2")
5256
assert v.bump_prerelease().bump_build() == expected
57+
assert v.compare(expected) == -1
5358

5459

5560
def test_should_versioninfo_bump_prerelease_and_build_with_token():
5661
v = parse_version_info("3.4.5-rc.1+b.1")
5762
expected = parse_version_info("3.4.5-rc.2+b.2")
5863
assert v.bump_prerelease().bump_build("b") == expected
64+
assert v.compare(expected) == -1
5965

6066

6167
def test_should_versioninfo_bump_multiple():
6268
v = parse_version_info("3.4.5-rc.1+build.1")
6369
expected = parse_version_info("3.4.5-rc.2+build.2")
6470
assert v.bump_prerelease().bump_build().bump_build() == expected
71+
assert v.compare(expected) == -1
6572
expected = parse_version_info("3.4.5-rc.3")
6673
assert v.bump_prerelease().bump_build().bump_build().bump_prerelease() == expected
74+
assert v.compare(expected) == -1
6775

6876

6977
def test_should_versioninfo_bump_prerelease_with_empty_str():
7078
v = parse_version_info("3.4.5")
7179
expected = parse_version_info("3.4.5-1")
7280
assert v.bump_prerelease("") == expected
81+
assert v.compare(expected) == 1
7382

7483

7584
def test_should_versioninfo_bump_prerelease_with_none():
7685
v = parse_version_info("3.4.5")
7786
expected = parse_version_info("3.4.5-rc.1")
7887
assert v.bump_prerelease(None) == expected
88+
assert v.compare(expected) == 1
89+
90+
91+
def test_should_versioninfo_bump_prerelease_nonnumeric():
92+
v = parse_version_info("3.4.5-rc1")
93+
expected = parse_version_info("3.4.5-rc1.0")
94+
assert v.bump_prerelease(None) == expected
95+
assert v.compare(expected) == -1
96+
97+
98+
def test_should_versioninfo_bump_prerelease_nonnumeric_nine():
99+
v = parse_version_info("3.4.5-rc9")
100+
expected = parse_version_info("3.4.5-rc9.0")
101+
assert v.bump_prerelease(None) == expected
102+
assert v.compare(expected) == -1
103+
104+
105+
def test_should_versioninfo_bump_prerelease_bump_patch():
106+
v = parse_version_info("3.4.5")
107+
expected = parse_version_info("3.4.6-rc.1")
108+
assert v.bump_prerelease(bump_when_empty=True) == expected
109+
assert v.compare(expected) == -1
110+
111+
112+
def test_should_versioninfo_bump_patch_and_prerelease_bump_patch():
113+
v = parse_version_info("3.4.5")
114+
expected = parse_version_info("3.4.7-rc.1")
115+
assert v.bump_patch().bump_prerelease(bump_when_empty=True) == expected
116+
assert v.compare(expected) == -1
79117

80118

81119
def test_should_versioninfo_bump_build_with_empty_str():
82120
v = parse_version_info("3.4.5")
83121
expected = parse_version_info("3.4.5+1")
84122
assert v.bump_build("") == expected
123+
assert v.compare(expected) == 0
85124

86125

87126
def test_should_versioninfo_bump_build_with_none():
88127
v = parse_version_info("3.4.5")
89128
expected = parse_version_info("3.4.5+build.1")
90129
assert v.bump_build(None) == expected
130+
assert v.compare(expected) == 0
91131

92132

93133
def test_should_ignore_extensions_for_bump():
94134
assert bump_patch("3.4.5-rc1+build4") == "3.4.6"
95135

96136

97137
@pytest.mark.parametrize(
98-
"version,token,expected",
138+
"version,token,expected,expected_compare",
99139
[
100-
("3.4.5-rc.9", None, "3.4.5-rc.10"),
101-
("3.4.5", None, "3.4.5-rc.1"),
102-
("3.4.5", "dev", "3.4.5-dev.1"),
103-
("3.4.5", "", "3.4.5-rc.1"),
140+
("3.4.5-rc.9", None, "3.4.5-rc.10", -1),
141+
("3.4.5", None, "3.4.5-rc.1", 1),
142+
("3.4.5", "dev", "3.4.5-dev.1", 1),
143+
("3.4.5", "", "3.4.5-rc.1", 1),
104144
],
105145
)
106-
def test_should_bump_prerelease(version, token, expected):
146+
def test_should_bump_prerelease(version, token, expected, expected_compare):
107147
token = "rc" if not token else token
108148
assert bump_prerelease(version, token) == expected
109-
149+
assert compare(version, expected) == expected_compare
110150

111151
def test_should_ignore_build_on_prerelease_bump():
112152
assert bump_prerelease("3.4.5-rc.1+build.4") == "3.4.5-rc.2"
@@ -123,3 +163,4 @@ def test_should_ignore_build_on_prerelease_bump():
123163
)
124164
def test_should_bump_build(version, expected):
125165
assert bump_build(version) == expected
166+
assert compare(version, expected) == 0

‎tests/test_pysemver-cli.py

Copy file name to clipboardExpand all lines: tests/test_pysemver-cli.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_should_parse_cli_arguments(cli, expected):
5555
(
5656
cmd_bump,
5757
Namespace(bump="prerelease", version="1.2.3-rc1"),
58-
does_not_raise("1.2.3-rc2"),
58+
does_not_raise("1.2.3-rc1.0"),
5959
),
6060
(
6161
cmd_bump,

‎tox.ini

Copy file name to clipboardExpand all lines: tox.ini
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ deps =
2929
setuptools-scm
3030
setenv =
3131
PIP_DISABLE_PIP_VERSION_CHECK = 1
32+
downloads = true
3233

3334

3435
[testenv:mypy]

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.