Skip to content

Commit e468144

Browse files
committed
Add from_4_digits method to Version class for parsing 4-part version strings
1 parent 8becb9b commit e468144

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

version/tests/version_tests.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,90 @@ def test_setters_invalid_values():
402402
v.prerelease = "invalid@prerelease"
403403
with pytest.raises(ValueError):
404404
v.metadata = "invalid@meta"
405+
406+
407+
@pytest.mark.parametrize(
408+
"input_str,expected_major,expected_minor,expected_patch,expected_prerelease, expected_metadata",
409+
[
410+
# Happy path
411+
("1.2.3.4", 1, 2, 3, "4", None),
412+
("12.0.456.7", 12, 0, 456, "7", None),
413+
("0.0.0.0", 0, 0, 0, "0", None),
414+
("999.888.777.666", 999, 888, 777, "666", None),
415+
("10.20.30.alpha", 10, 20, 30, "alpha", None),
416+
("1.2.3.4+patch", 1, 2, 3, "4", "patch"),
417+
418+
("1.2.3.4.5", 1, 2, 3, "4.5", None),
419+
("1.2.3.4.5.6", 1, 2, 3, "4.5.6", None),
420+
],
421+
ids=[
422+
"simple-numeric",
423+
"zero-minor",
424+
"all-zeros",
425+
"large-numbers",
426+
"prerelease-string",
427+
"simple-numeric-metadata",
428+
429+
"too-many-parts",
430+
"way-too-many-parts",
431+
]
432+
)
433+
def test_from_4_digits_happy_path(input_str, expected_major, expected_minor, expected_patch, expected_prerelease, expected_metadata):
434+
435+
# Act
436+
result = Version.from_4_digits(input_str)
437+
438+
# Assert
439+
assert isinstance(result, Version)
440+
assert result.major == expected_major
441+
assert result.minor == expected_minor
442+
assert result.patch == expected_patch
443+
assert result.prerelease == expected_prerelease
444+
assert result.metadata == expected_metadata
445+
446+
447+
@pytest.mark.parametrize(
448+
"input_str",
449+
[
450+
"1.2.3", # Too few parts
451+
"1.2", # Way too few
452+
"", # Empty string
453+
"1.2.3.",
454+
".2.3.4",
455+
"1..3.4",
456+
"1.2..4",
457+
],
458+
ids=[
459+
"too-few-parts",
460+
"way-too-few",
461+
"empty-string",
462+
"trailing-dot-empty-prerelease",
463+
"leading-dot-empty-major",
464+
"empty-minor",
465+
"empty-patch",
466+
]
467+
)
468+
def test_from_4_digits_invalid_parts_count(input_str):
469+
470+
# Act & Assert
471+
with pytest.raises(ValueError) as excinfo:
472+
print(Version.from_4_digits(input_str))
473+
assert f"Invalid version string: {input_str}" in str(excinfo.value)
474+
475+
476+
# test Version.from_string with 4 digits
477+
def test_from_string_4_digits():
478+
version = Version.from_string("1.2.3.4")
479+
assert version.major == 1
480+
assert version.minor == 2
481+
assert version.patch == 3
482+
assert version.prerelease == "4"
483+
assert version.metadata is None
484+
485+
def test_from_string_4_digits_with_metadata():
486+
version = Version.from_string("1.2.3.4+build.1")
487+
assert version.major == 1
488+
assert version.minor == 2
489+
assert version.patch == 3
490+
assert version.prerelease == "4"
491+
assert version.metadata == "build.1"

version/version/version.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ class Version:
3232
+ r'(?:\+' + __VERSION_METADATA + r')?'
3333
+ '$')
3434
_RE_VERSION = re.compile(__VERSION)
35+
_4_DIGITS_VERSION = ( '^' + __VERSION_MAJOR
36+
+ r'\.'
37+
+ __VERSION_MINOR
38+
+ r'\.'
39+
+ __VERSION_PATCH
40+
+ r'\.'
41+
+ __VERSION_PRERELEASE
42+
+ r'(?:\+' + __VERSION_METADATA + r')?'
43+
+ '$')
44+
_RE_4_DIGITS_VERSION = re.compile(_4_DIGITS_VERSION)
3545

3646

3747
def __init__(self,
@@ -73,6 +83,44 @@ def __init__(self,
7383
self.__prerelease = prerelease
7484
self.__metadata = metadata
7585

86+
@classmethod
87+
def from_4_digits(cls, version_str: str):
88+
"""
89+
Create a Version object from a 4 digits version string.
90+
91+
:param version: 4 digits version string, separated by dots (e.g. "12.3.456.7")
92+
:return: Version object
93+
"""
94+
# if not isinstance(version, str):
95+
# raise ValueError(f"Invalid version string: {version}")
96+
# parts = version.split('.')
97+
# if len(parts) != 4:
98+
# raise ValueError(f"Invalid version string: {version}")
99+
# major = int(parts[0] or 0)
100+
# minor = int(parts[1] or 0)
101+
# patch = int(parts[2] or 0)
102+
103+
# if "+" in parts[3]:
104+
# prerelease, metadata = parts[3].split("+", 1)
105+
# else:
106+
# metadata = None
107+
# prerelease = parts[3]
108+
109+
# prerelease = prerelease or None
110+
# metadata = metadata or None
111+
# return Version(major, minor, patch, prerelease, metadata)
112+
match = cls._RE_4_DIGITS_VERSION.match(version_str)
113+
if not match:
114+
raise ValueError(f"Invalid version string: {version_str}")
115+
116+
major = int(match.group('major') or 0)
117+
minor = int(match.group('minor') or 0)
118+
patch = int(match.group('patch') or 0)
119+
prerelease = match.group('prerelease') or None
120+
metadata = match.group('metadata') or None
121+
122+
return cls(major, minor, patch, prerelease, metadata)
123+
76124
@classmethod
77125
def from_string(cls, version_str: str):
78126
"""
@@ -81,6 +129,9 @@ def from_string(cls, version_str: str):
81129
:param version_str: Version string
82130
:return: Version object
83131
"""
132+
if cls._RE_4_DIGITS_VERSION.match(version_str):
133+
return cls.from_4_digits(version_str)
134+
84135
match = cls._RE_VERSION.match(version_str)
85136
if not match:
86137
raise ValueError(f"Invalid version string: {version_str}")

0 commit comments

Comments
 (0)