Skip to content

Commit f2c23f6

Browse files
tomschrscls19fr
authored andcommitted
Fix #208: Introduce VersionInfo.isvalid() function (#209)
* VersionInfo.isvalid(cls, version:str) -> bool * Add test case * Describe function in documentation * Amend pysemver script with "check" subcommand * Update manpage (pysemver.rst) * Update `CHANGELOG.rst`
1 parent 445f47a commit f2c23f6

File tree

5 files changed

+127
-14
lines changed

5 files changed

+127
-14
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ Features
2020
* :gh:`191` (:pr:`194`): Created manpage for pysemver
2121
* :gh:`196` (:pr:`197`): Added distribution specific installation instructions
2222
* :gh:`201` (:pr:`202`): Reformatted source code with black
23+
* :gh:`208` (:pr:`209`): Introduce new function :func:`semver.VersionInfo.isvalid`
24+
and extend :command:`pysemver` with :command:`check` subcommand
25+
2326

2427
Bug Fixes
2528
---------
@@ -54,7 +57,6 @@ Features
5457
* :pr:`166`: Reworked :file:`.gitignore` file
5558
* :gh:`167` (:pr:`168`): Introduced global constant :data:`SEMVER_SPEC_VERSION`
5659

57-
5860
Bug Fixes
5961
---------
6062

docs/pysemver.rst

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,29 @@ you get an error message and a return code != 0::
8686
ERROR 1.5 is not valid SemVer string
8787

8888

89+
pysemver check
90+
~~~~~~~~~~~~~~
91+
92+
Checks if a string is a valid semver version.
93+
94+
.. code:: bash
95+
96+
pysemver check <VERSION>
97+
98+
.. option:: <VERSION>
99+
100+
The version string to check.
101+
102+
The *error code* returned by the script indicates if the
103+
version is valid (=0) or not (!=0)::
104+
105+
$ pysemver check 1.2.3; echo $?
106+
0
107+
$ pysemver check 2.1; echo $?
108+
ERROR Invalid version '2.1'
109+
2
110+
111+
89112
pysemver compare
90113
~~~~~~~~~~~~~~~~
91114

@@ -121,7 +144,6 @@ are valid (return code 0) or not (return code != 0)::
121144
ERROR 1.2.x is not valid SemVer string
122145
2
123146

124-
125147
See also
126148
--------
127149

docs/usage.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ A version can be created in different ways:
5353
>>> semver.VersionInfo(1, 2, 3, 4, 5)
5454
VersionInfo(major=1, minor=2, patch=3, prerelease=4, build=5)
5555

56+
If you pass an invalid version string you will get a ``ValueError``::
57+
58+
>>> semver.parse("1.2")
59+
Traceback (most recent call last)
60+
...
61+
ValueError: 1.2 is not valid SemVer string
62+
5663

5764
Parsing a Version String
5865
------------------------
@@ -77,6 +84,20 @@ Parsing a Version String
7784
{'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'}
7885

7986

87+
Checking for a Valid Semver Version
88+
-----------------------------------
89+
90+
If you need to check a string if it is a valid semver version, use the
91+
classmethod :func:`semver.VersionInfo.isvalid`:
92+
93+
.. code-block:: python
94+
95+
>>> VersionInfo.isvalid("1.0.0")
96+
True
97+
>>> VersionInfo.isvalid("invalid")
98+
False
99+
100+
80101
Accessing Parts of a Version
81102
----------------------------
82103

semver.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,21 @@ def replace(self, **parts):
321321
)
322322
raise TypeError(error)
323323

324+
@classmethod
325+
def isvalid(cls, version):
326+
"""Check if the string is a valid semver version
327+
328+
:param str version: the version string to check
329+
:return: True if the version string is a valid semver version, False
330+
otherwise.
331+
:rtype: bool
332+
"""
333+
try:
334+
cls.parse(version)
335+
return True
336+
except ValueError:
337+
return False
338+
324339

325340
def _to_dict(obj):
326341
if isinstance(obj, VersionInfo):
@@ -681,6 +696,14 @@ def createparser():
681696
sb.add_parser("build", help="Bump the build part of the version"),
682697
):
683698
p.add_argument("version", help="Version to raise")
699+
700+
# Create the check subcommand
701+
parser_check = s.add_parser(
702+
"check", help="Checks if a string is a valid semver version"
703+
)
704+
parser_check.set_defaults(which="check")
705+
parser_check.add_argument("version", help="Version to check")
706+
684707
return parser
685708

686709

@@ -697,6 +720,7 @@ def process(args):
697720
if not hasattr(args, "which"):
698721
args.parser.print_help()
699722
raise SystemExit()
723+
700724
elif args.which == "bump":
701725
maptable = {
702726
"major": "bump_major",
@@ -718,6 +742,11 @@ def process(args):
718742
elif args.which == "compare":
719743
return str(compare(args.version1, args.version2))
720744

745+
elif args.which == "check":
746+
if VersionInfo.isvalid(args.version):
747+
return None
748+
raise ValueError("Invalid version %r" % args.version)
749+
721750

722751
def main(cliargs=None):
723752
"""Entry point for the application script
@@ -732,7 +761,8 @@ def main(cliargs=None):
732761
# Save parser instance:
733762
args.parser = parser
734763
result = process(args)
735-
print(result)
764+
if result is not None:
765+
print(result)
736766
return 0
737767

738768
except (ValueError, TypeError) as err:

test_semver.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from argparse import Namespace
2+
from contextlib import contextmanager
23
import pytest # noqa
34

45
from semver import (
@@ -41,6 +42,11 @@
4142
]
4243

4344

45+
@contextmanager
46+
def does_not_raise(item):
47+
yield item
48+
49+
4450
@pytest.mark.parametrize(
4551
"string,expected", [("rc", "rc"), ("rc.1", "rc.2"), ("2x", "3x")]
4652
)
@@ -703,6 +709,8 @@ def test_should_be_able_to_use_integers_as_prerelease_build():
703709
["compare", "1.2.3", "2.1.3"],
704710
Namespace(which="compare", version1="1.2.3", version2="2.1.3"),
705711
),
712+
# ---
713+
(["check", "1.2.3"], Namespace(which="check", version="1.2.3")),
706714
],
707715
)
708716
def test_should_parse_cli_arguments(cli, expected):
@@ -713,25 +721,50 @@ def test_should_parse_cli_arguments(cli, expected):
713721

714722

715723
@pytest.mark.parametrize(
716-
"args,expected",
724+
"args,expectation",
717725
[
718726
# bump subcommand
719-
(Namespace(which="bump", bump="major", version="1.2.3"), "2.0.0"),
720-
(Namespace(which="bump", bump="minor", version="1.2.3"), "1.3.0"),
721-
(Namespace(which="bump", bump="patch", version="1.2.3"), "1.2.4"),
722-
(Namespace(which="bump", bump="prerelease", version="1.2.3-rc1"), "1.2.3-rc2"),
727+
(
728+
Namespace(which="bump", bump="major", version="1.2.3"),
729+
does_not_raise("2.0.0"),
730+
),
731+
(
732+
Namespace(which="bump", bump="minor", version="1.2.3"),
733+
does_not_raise("1.3.0"),
734+
),
735+
(
736+
Namespace(which="bump", bump="patch", version="1.2.3"),
737+
does_not_raise("1.2.4"),
738+
),
739+
(
740+
Namespace(which="bump", bump="prerelease", version="1.2.3-rc1"),
741+
does_not_raise("1.2.3-rc2"),
742+
),
723743
(
724744
Namespace(which="bump", bump="build", version="1.2.3+build.13"),
725-
"1.2.3+build.14",
745+
does_not_raise("1.2.3+build.14"),
726746
),
727747
# compare subcommand
728-
(Namespace(which="compare", version1="1.2.3", version2="2.1.3"), "-1"),
729-
(Namespace(which="compare", version1="1.2.3", version2="1.2.3"), "0"),
730-
(Namespace(which="compare", version1="2.4.0", version2="2.1.3"), "1"),
748+
(
749+
Namespace(which="compare", version1="1.2.3", version2="2.1.3"),
750+
does_not_raise("-1"),
751+
),
752+
(
753+
Namespace(which="compare", version1="1.2.3", version2="1.2.3"),
754+
does_not_raise("0"),
755+
),
756+
(
757+
Namespace(which="compare", version1="2.4.0", version2="2.1.3"),
758+
does_not_raise("1"),
759+
),
760+
# check subcommand
761+
(Namespace(which="check", version="1.2.3"), does_not_raise(None)),
762+
(Namespace(which="check", version="1.2"), pytest.raises(ValueError)),
731763
],
732764
)
733-
def test_should_process_parsed_cli_arguments(args, expected):
734-
assert process(args) == expected
765+
def test_should_process_parsed_cli_arguments(args, expectation):
766+
with expectation as expected:
767+
assert process(args) == expected
735768

736769

737770
def test_should_process_print(capsys):
@@ -803,3 +836,8 @@ def test_should_return_versioninfo_with_replaced_parts(version, parts, expected)
803836
def test_replace_raises_ValueError_for_non_numeric_values():
804837
with pytest.raises(ValueError):
805838
VersionInfo.parse("1.2.3").replace(major="x")
839+
840+
841+
def test_should_versioninfo_isvalid():
842+
assert VersionInfo.isvalid("1.0.0") is True
843+
assert VersionInfo.isvalid("foo") is False

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy