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 8d4336e

Browse filesBrowse files
authored
Merge pull request #222 from tomschr/feature/221-bump-prerelease-and-build
First implementation of next_version
2 parents d69e7c4 + 5b04960 commit 8d4336e
Copy full SHA for 8d4336e

File tree

3 files changed

+136
-3
lines changed
Filter options

3 files changed

+136
-3
lines changed

‎docs/usage.rst

Copy file name to clipboardExpand all lines: docs/usage.rst
+26-2Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ It is possible to convert a :class:`semver.VersionInfo` instance:
244244
(5, 4, 2, None, None)
245245

246246

247-
Increasing Parts of a Version
248-
-----------------------------
247+
Raising Parts of a Version
248+
--------------------------
249249

250250
The ``semver`` module contains the following functions to raise parts of
251251
a version:
@@ -276,6 +276,30 @@ a version:
276276
Likewise the module level functions :func:`semver.bump_major`.
277277

278278

279+
Increasing Parts of a Version Taking into Account Prereleases
280+
-------------------------------------------------------------
281+
282+
.. versionadded:: 2.10.0
283+
Added :func:`semver.VersionInfo.next_version`.
284+
285+
If you want to raise your version and take prereleases into account,
286+
the function :func:`semver.VersionInfo.next_version` would perhaps a
287+
better fit.
288+
289+
290+
.. code-block:: python
291+
292+
>>> v = semver.VersionInfo.parse("3.4.5-pre.2+build.4")
293+
>>> str(v.next_version(part="prerelease"))
294+
'3.4.5-pre.3'
295+
>>> str(semver.VersionInfo.parse("3.4.5-pre.2+build.4").next_version(part="patch"))
296+
'3.4.5'
297+
>>> str(semver.VersionInfo.parse("3.4.5+build.4").next_version(part="patch"))
298+
'3.4.5'
299+
>>> str(semver.VersionInfo.parse("0.1.4").next_version("prerelease"))
300+
'0.1.5-rc.1'
301+
302+
279303
Comparing Versions
280304
------------------
281305

‎semver.py

Copy file name to clipboardExpand all lines: semver.py
+74-1Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,53 @@ def compare(self, other):
422422

423423
return rccmp
424424

425+
def next_version(self, part, prerelease_token="rc"):
426+
"""
427+
Determines next version, preserving natural order.
428+
429+
.. versionadded:: 2.10.0
430+
431+
This function is taking prereleases into account.
432+
The "major", "minor", and "patch" raises the respective parts like
433+
the ``bump_*`` functions. The real difference is using the
434+
"preprelease" part. It gives you the next patch version of the prerelease,
435+
for example:
436+
437+
>>> str(semver.VersionInfo.parse("0.1.4").next_version("prerelease"))
438+
'0.1.5-rc.1'
439+
440+
:param part: One of "major", "minor", "patch", or "prerelease"
441+
:param prerelease_token: prefix string of prerelease, defaults to 'rc'
442+
:return:
443+
"""
444+
validparts = {
445+
"major",
446+
"minor",
447+
"patch",
448+
"prerelease",
449+
# "build", # currently not used
450+
}
451+
if part not in validparts:
452+
raise ValueError(
453+
"Invalid part. Expected one of {validparts}, but got {part!r}".format(
454+
validparts=validparts, part=part
455+
)
456+
)
457+
version = self
458+
if (version.prerelease or version.build) and (
459+
part == "patch"
460+
or (part == "minor" and version.patch == 0)
461+
or (part == "major" and version.minor == version.patch == 0)
462+
):
463+
return version.replace(prerelease=None, build=None)
464+
465+
if part in ("major", "minor", "patch"):
466+
return str(getattr(version, "bump_" + part)())
467+
468+
if not version.prerelease:
469+
version = version.bump_patch()
470+
return version.bump_prerelease(prerelease_token)
471+
425472
@comparator
426473
def __eq__(self, other):
427474
return self.compare(other) == 0
@@ -709,7 +756,10 @@ def max_ver(ver1, ver2):
709756
>>> semver.max_ver("1.0.0", "2.0.0")
710757
'2.0.0'
711758
"""
712-
ver1 = VersionInfo.parse(ver1)
759+
if isinstance(ver1, str):
760+
ver1 = VersionInfo.parse(ver1)
761+
elif not isinstance(ver1, VersionInfo):
762+
raise TypeError()
713763
cmp_res = ver1.compare(ver2)
714764
if cmp_res >= 0:
715765
return str(ver1)
@@ -898,6 +948,7 @@ def replace(version, **parts):
898948
return str(VersionInfo.parse(version).replace(**parts))
899949

900950

951+
# ---- CLI
901952
def cmd_bump(args):
902953
"""
903954
Subcommand: Bumps a version.
@@ -953,6 +1004,19 @@ def cmd_compare(args):
9531004
return str(compare(args.version1, args.version2))
9541005

9551006

1007+
def cmd_nextver(args):
1008+
"""
1009+
Subcommand: Determines the next version, taking prereleases into account.
1010+
1011+
Synopsis: nextver <VERSION> <PART>
1012+
1013+
:param args: The parsed arguments
1014+
:type args: :class:`argparse.Namespace`
1015+
"""
1016+
version = VersionInfo.parse(args.version)
1017+
return str(version.next_version(args.part))
1018+
1019+
9561020
def createparser():
9571021
"""
9581022
Create an :class:`argparse.ArgumentParser` instance.
@@ -995,6 +1059,15 @@ def createparser():
9951059
parser_check.set_defaults(func=cmd_check)
9961060
parser_check.add_argument("version", help="Version to check")
9971061

1062+
# Create the nextver subcommand
1063+
parser_nextver = s.add_parser(
1064+
"nextver", help="Determines the next version, taking prereleases into account."
1065+
)
1066+
parser_nextver.set_defaults(func=cmd_nextver)
1067+
parser_nextver.add_argument("version", help="Version to raise")
1068+
parser_nextver.add_argument(
1069+
"part", help="One of 'major', 'minor', 'patch', or 'prerelease'"
1070+
)
9981071
return parser
9991072

10001073

‎test_semver.py

Copy file name to clipboardExpand all lines: test_semver.py
+36Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,3 +881,39 @@ def mock_func():
881881

882882
with pytest.deprecated_call():
883883
assert mock_func()
884+
885+
886+
def test_next_version_with_invalid_parts():
887+
version = VersionInfo.parse("1.0.1")
888+
with pytest.raises(ValueError):
889+
version.next_version("invalid")
890+
891+
892+
@pytest.mark.parametrize(
893+
"version, part, expected",
894+
[
895+
# major
896+
("1.0.4-rc.1", "major", "2.0.0"),
897+
("1.1.0-rc.1", "major", "2.0.0"),
898+
("1.1.4-rc.1", "major", "2.0.0"),
899+
("1.2.3", "major", "2.0.0"),
900+
("1.0.0-rc.1", "major", "1.0.0"),
901+
# minor
902+
("0.2.0-rc.1", "minor", "0.2.0"),
903+
("0.2.5-rc.1", "minor", "0.3.0"),
904+
("1.3.1", "minor", "1.4.0"),
905+
# patch
906+
("1.3.2", "patch", "1.3.3"),
907+
("0.1.5-rc.2", "patch", "0.1.5"),
908+
# prerelease
909+
("0.1.4", "prerelease", "0.1.5-rc.1"),
910+
("0.1.5-rc.1", "prerelease", "0.1.5-rc.2"),
911+
# special cases
912+
("0.2.0-rc.1", "patch", "0.2.0"), # same as "minor"
913+
("1.0.0-rc.1", "patch", "1.0.0"), # same as "major"
914+
("1.0.0-rc.1", "minor", "1.0.0"), # same as "major"
915+
],
916+
)
917+
def test_next_version_with_versioninfo(version, part, expected):
918+
ver = VersionInfo.parse(version)
919+
assert str(ver.next_version(part)) == expected

0 commit comments

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