Skip to content

Navigation Menu

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 2aeb61b

Browse filesBrowse 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
Copy full SHA for 2aeb61b

File tree

4 files changed

+98
-9
lines changed
Filter options

4 files changed

+98
-9
lines changed

‎changelog.d/pr359.feature.rst

Copy file name to clipboard
+2Lines 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

Copy file name to clipboardExpand all lines: docs/usage/parse-version-string.rst
+7Lines 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

Copy file name to clipboardExpand all lines: src/semver/version.py
+39-9Lines 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

Copy file name to clipboardExpand all lines: tests/test_parsing.py
+50Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.