Skip to content

Commit 2aeb61b

Browse files
OidaTiftlatomschr
andauthored
Allow optional minor and patch parts (#359)
* Change Version.parse to allow optional minor and patch parts * Add documentation and changelog entry Co-authored-by: Tom Schraitle <tomschr@users.noreply.github.com>
1 parent d063887 commit 2aeb61b

File tree

4 files changed

+98
-9
lines changed

4 files changed

+98
-9
lines changed

changelog.d/pr359.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add optional parameter ``optional_minor_and_patch`` in :meth:`.Version.parse` to allow optional
2+
minor and patch parts.

docs/usage/parse-version-string.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@ Use the function :func:`Version.parse <semver.version.Version.parse>`::
66

77
>>> Version.parse("3.4.5-pre.2+build.4")
88
Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4')
9+
10+
Set the parameter ``optional_minor_and_patch=True`` to allow optional
11+
minor and patch parts. Optional parts are set to zero. By default (False), the
12+
version string to parse has to follow the semver specification::
13+
14+
>>> Version.parse("1.2", optional_minor_and_patch=True)
15+
Version(major=1, minor=2, patch=0, prerelease=None, build=None)

src/semver/version.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,19 @@ class Version:
6868
__slots__ = ("_major", "_minor", "_patch", "_prerelease", "_build")
6969
#: Regex for number in a prerelease
7070
_LAST_NUMBER = re.compile(r"(?:[^\d]*(\d+)[^\d]*)+")
71-
#: Regex for a semver version
72-
_REGEX = re.compile(
71+
#: Regex template for a semver version
72+
_REGEX_TEMPLATE = \
7373
r"""
7474
^
7575
(?P<major>0|[1-9]\d*)
76-
\.
77-
(?P<minor>0|[1-9]\d*)
78-
\.
79-
(?P<patch>0|[1-9]\d*)
76+
(?:
77+
\.
78+
(?P<minor>0|[1-9]\d*)
79+
(?:
80+
\.
81+
(?P<patch>0|[1-9]\d*)
82+
){opt_patch}
83+
){opt_minor}
8084
(?:-(?P<prerelease>
8185
(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
8286
(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
@@ -86,7 +90,15 @@ class Version:
8690
(?:\.[0-9a-zA-Z-]+)*
8791
))?
8892
$
89-
""",
93+
"""
94+
#: Regex for a semver version
95+
_REGEX = re.compile(
96+
_REGEX_TEMPLATE.format(opt_patch='', opt_minor=''),
97+
re.VERBOSE,
98+
)
99+
#: Regex for a semver version that might be shorter
100+
_REGEX_OPTIONAL_MINOR_AND_PATCH = re.compile(
101+
_REGEX_TEMPLATE.format(opt_patch='?', opt_minor='?'),
90102
re.VERBOSE,
91103
)
92104

@@ -553,15 +565,26 @@ def match(self, match_expr: str) -> bool:
553565
return cmp_res in possibilities
554566

555567
@classmethod
556-
def parse(cls, version: String) -> "Version":
568+
def parse(
569+
cls,
570+
version: String,
571+
optional_minor_and_patch: bool = False
572+
) -> "Version":
557573
"""
558574
Parse version string to a Version instance.
559575
560576
.. versionchanged:: 2.11.0
561577
Changed method from static to classmethod to
562578
allow subclasses.
579+
.. versionchanged:: 3.0.0
580+
Added optional parameter optional_minor_and_patch to allow optional
581+
minor and patch parts.
563582
564583
:param version: version string
584+
:param optional_minor_and_patch: if set to true, the version string to parse \
585+
can contain optional minor and patch parts. Optional parts are set to zero.
586+
By default (False), the version string to parse has to follow the semver
587+
specification.
565588
:return: a new :class:`Version` instance
566589
:raises ValueError: if version is invalid
567590
:raises TypeError: if version contains the wrong type
@@ -575,11 +598,18 @@ def parse(cls, version: String) -> "Version":
575598
elif not isinstance(version, String.__args__): # type: ignore
576599
raise TypeError("not expecting type '%s'" % type(version))
577600

578-
match = cls._REGEX.match(version)
601+
if optional_minor_and_patch:
602+
match = cls._REGEX_OPTIONAL_MINOR_AND_PATCH.match(version)
603+
else:
604+
match = cls._REGEX.match(version)
579605
if match is None:
580606
raise ValueError(f"{version} is not valid SemVer string")
581607

582608
matched_version_parts: Dict[str, Any] = match.groupdict()
609+
if not matched_version_parts['minor']:
610+
matched_version_parts['minor'] = 0
611+
if not matched_version_parts['patch']:
612+
matched_version_parts['patch'] = 0
583613

584614
return cls(**matched_version_parts)
585615

tests/test_parsing.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,56 @@ def test_should_parse_version(version, expected):
5353
assert result == expected
5454

5555

56+
@pytest.mark.parametrize(
57+
"version,expected",
58+
[
59+
# no. 1
60+
(
61+
"1.2-alpha.1.2+build.11.e0f985a",
62+
{
63+
"major": 1,
64+
"minor": 2,
65+
"patch": 0,
66+
"prerelease": "alpha.1.2",
67+
"build": "build.11.e0f985a",
68+
},
69+
),
70+
# no. 2
71+
(
72+
"1-alpha-1+build.11.e0f985a",
73+
{
74+
"major": 1,
75+
"minor": 0,
76+
"patch": 0,
77+
"prerelease": "alpha-1",
78+
"build": "build.11.e0f985a",
79+
},
80+
),
81+
(
82+
"0.1-0f",
83+
{"major": 0, "minor": 1, "patch": 0, "prerelease": "0f", "build": None},
84+
),
85+
(
86+
"0-0foo.1",
87+
{"major": 0, "minor": 0, "patch": 0, "prerelease": "0foo.1", "build": None},
88+
),
89+
(
90+
"0-0foo.1+build.1",
91+
{
92+
"major": 0,
93+
"minor": 0,
94+
"patch": 0,
95+
"prerelease": "0foo.1",
96+
"build": "build.1",
97+
},
98+
),
99+
],
100+
)
101+
def test_should_parse_version_with_optional_minor_and_patch(version, expected):
102+
result = Version.parse(version, optional_minor_and_patch=True)
103+
assert result == expected
104+
105+
56106
def test_parse_version_info_str_hash():
57107
s_version = "1.2.3-alpha.1.2+build.11.e0f985a"
58108
v = parse_version_info(s_version)

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