Skip to content

Commit f7875f0

Browse files
authored
♻️ update SemanticVersion code (#237)
* Derive SemanticVersion from semver.Version This change makes it possible to use SemanticVersion to initialize models using the __init__ method without type errors. * Add pattern to SemanticVersion's JSON Schema This ensures that users of the JSON Schema also get their semantic versions validated. The pattern and the new test cases come from https://semver.org.
1 parent c0ad1b5 commit f7875f0

File tree

3 files changed

+103
-9
lines changed

3 files changed

+103
-9
lines changed

pydantic_extra_types/semantic_version.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
) from e
1717

1818

19-
class SemanticVersion:
19+
class SemanticVersion(semver.Version):
2020
"""
2121
Semantic version based on the official [semver thread](https://python-semver.readthedocs.io/en/latest/advanced/combine-pydantic-and-semver.html).
2222
"""
@@ -27,8 +27,8 @@ def __get_pydantic_core_schema__(
2727
_source_type: Any,
2828
_handler: Callable[[Any], core_schema.CoreSchema],
2929
) -> core_schema.CoreSchema:
30-
def validate_from_str(value: str) -> semver.Version:
31-
return semver.Version.parse(value)
30+
def validate_from_str(value: str) -> SemanticVersion:
31+
return cls.parse(value)
3232

3333
from_str_schema = core_schema.chain_schema(
3434
[
@@ -52,4 +52,8 @@ def validate_from_str(value: str) -> semver.Version:
5252
def __get_pydantic_json_schema__(
5353
cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
5454
) -> JsonSchemaValue:
55-
return handler(core_schema.str_schema())
55+
return handler(
56+
core_schema.str_schema(
57+
pattern=r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'
58+
)
59+
)

tests/test_json_schema.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,13 @@
349349
(
350350
SemanticVersion,
351351
{
352-
'properties': {'x': {'title': 'X', 'type': 'string'}},
352+
'properties': {
353+
'x': {
354+
'title': 'X',
355+
'type': 'string',
356+
'pattern': r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$',
357+
}
358+
},
353359
'required': ['x'],
354360
'title': 'Model',
355361
'type': 'object',

tests/test_semantic_version.py

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
import semver
23
from pydantic import BaseModel, ValidationError
34

45
from pydantic_extra_types.semantic_version import SemanticVersion
@@ -12,14 +13,97 @@ class Application(BaseModel):
1213
return Application
1314

1415

15-
@pytest.mark.parametrize('version', ['1.0.0', '1.0.0-alpha.1', '1.0.0-alpha.1+build.1', '1.2.3'])
16-
def test_valid_semantic_version(SemanticVersionObject, version):
17-
application = SemanticVersionObject(version=version)
16+
@pytest.mark.parametrize(
17+
'constructor', [str, semver.Version.parse, SemanticVersion.parse], ids=['str', 'semver.Version', 'SemanticVersion']
18+
)
19+
@pytest.mark.parametrize(
20+
'version',
21+
[
22+
'0.0.4',
23+
'1.2.3',
24+
'10.20.30',
25+
'1.1.2-prerelease+meta',
26+
'1.1.2+meta',
27+
'1.1.2+meta-valid',
28+
'1.0.0-alpha',
29+
'1.0.0-beta',
30+
'1.0.0-alpha.beta',
31+
'1.0.0-alpha.beta.1',
32+
'1.0.0-alpha.1',
33+
'1.0.0-alpha0.valid',
34+
'1.0.0-alpha.0valid',
35+
'1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay',
36+
'1.0.0-rc.1+build.1',
37+
'2.0.0-rc.1+build.123',
38+
'1.2.3-beta',
39+
'10.2.3-DEV-SNAPSHOT',
40+
'1.2.3-SNAPSHOT-123',
41+
'1.0.0',
42+
'2.0.0',
43+
'1.1.7',
44+
'2.0.0+build.1848',
45+
'2.0.1-alpha.1227',
46+
'1.0.0-alpha+beta',
47+
'1.2.3----RC-SNAPSHOT.12.9.1--.12+788',
48+
'1.2.3----R-S.12.9.1--.12+meta',
49+
'1.2.3----RC-SNAPSHOT.12.9.1--.12',
50+
'1.0.0+0.build.1-rc.10000aaa-kk-0.1',
51+
'99999999999999999999999.999999999999999999.99999999999999999',
52+
'1.0.0-0A.is.legal',
53+
],
54+
)
55+
def test_valid_semantic_version(SemanticVersionObject, constructor, version):
56+
application = SemanticVersionObject(version=constructor(version))
1857
assert application.version
1958
assert application.model_dump() == {'version': version}
2059

2160

22-
@pytest.mark.parametrize('invalid_version', ['no dots string', 'with.dots.string', ''])
61+
@pytest.mark.parametrize(
62+
'invalid_version',
63+
[
64+
'',
65+
'1',
66+
'1.2',
67+
'1.2.3-0123',
68+
'1.2.3-0123.0123',
69+
'1.1.2+.123',
70+
'+invalid',
71+
'-invalid',
72+
'-invalid+invalid',
73+
'-invalid.01',
74+
'alpha',
75+
'alpha.beta',
76+
'alpha.beta.1',
77+
'alpha.1',
78+
'alpha+beta',
79+
'alpha_beta',
80+
'alpha.',
81+
'alpha..',
82+
'beta',
83+
'1.0.0-alpha_beta',
84+
'-alpha.',
85+
'1.0.0-alpha..',
86+
'1.0.0-alpha..1',
87+
'1.0.0-alpha...1',
88+
'1.0.0-alpha....1',
89+
'1.0.0-alpha.....1',
90+
'1.0.0-alpha......1',
91+
'1.0.0-alpha.......1',
92+
'01.1.1',
93+
'1.01.1',
94+
'1.1.01',
95+
'1.2',
96+
'1.2.3.DEV',
97+
'1.2-SNAPSHOT',
98+
'1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788',
99+
'1.2-RC-SNAPSHOT',
100+
'-1.0.3-gamma+b7718',
101+
'+justmeta',
102+
'9.8.7+meta+meta',
103+
'9.8.7-whatever+meta+meta',
104+
'99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12',
105+
],
106+
)
23107
def test_invalid_semantic_version(SemanticVersionObject, invalid_version):
24108
with pytest.raises(ValidationError):
25109
SemanticVersionObject(version=invalid_version)

0 commit comments

Comments
 (0)