Skip to content

Commit 1cddee1

Browse files
Add Semantic version type (#195)
* Add Semver as a dependency * Add SemanticVersion type including tests * Increase test coverage --------- Co-authored-by: Yasser Tahiri <[email protected]>
1 parent 55c2fcc commit 1cddee1

File tree

5 files changed

+92
-0
lines changed

5 files changed

+92
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
SemanticVersion definition that is based on the Semantiv Versioning Specification [semver](https://semver.org/).
3+
"""
4+
5+
from typing import Any, Callable
6+
7+
from pydantic import GetJsonSchemaHandler
8+
from pydantic.json_schema import JsonSchemaValue
9+
from pydantic_core import core_schema
10+
11+
try:
12+
import semver
13+
except ModuleNotFoundError as e: # pragma: no cover
14+
raise RuntimeError(
15+
'The `semantic_version` module requires "semver" to be installed. You can install it with "pip install semver".'
16+
) from e
17+
18+
19+
class SemanticVersion:
20+
"""
21+
Semantic version based on the official [semver thread](https://python-semver.readthedocs.io/en/latest/advanced/combine-pydantic-and-semver.html).
22+
"""
23+
24+
@classmethod
25+
def __get_pydantic_core_schema__(
26+
cls,
27+
_source_type: Any,
28+
_handler: Callable[[Any], core_schema.CoreSchema],
29+
) -> core_schema.CoreSchema:
30+
def validate_from_str(value: str) -> semver.Version:
31+
return semver.Version.parse(value)
32+
33+
from_str_schema = core_schema.chain_schema(
34+
[
35+
core_schema.str_schema(),
36+
core_schema.no_info_plain_validator_function(validate_from_str),
37+
]
38+
)
39+
40+
return core_schema.json_or_python_schema(
41+
json_schema=from_str_schema,
42+
python_schema=core_schema.union_schema(
43+
[
44+
core_schema.is_instance_schema(semver.Version),
45+
from_str_schema,
46+
]
47+
),
48+
serialization=core_schema.to_string_ser_schema(),
49+
)
50+
51+
@classmethod
52+
def __get_pydantic_json_schema__(
53+
cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
54+
) -> JsonSchemaValue:
55+
return handler(core_schema.str_schema())

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ dynamic = ['version']
4747
all = [
4848
'phonenumbers>=8,<9',
4949
'pycountry>=23',
50+
'semver>=3.0.2',
5051
'python-ulid>=1,<2; python_version<"3.9"',
5152
'python-ulid>=1,<3; python_version>="3.9"',
5253
'pendulum>=3.0.0,<4.0.0'
5354
]
5455
phonenumbers = ['phonenumbers>=8,<9']
5556
pycountry = ['pycountry>=23']
57+
semver = ['semver>=3.0.2']
5658
python_ulid = [
5759
'python-ulid>=1,<2; python_version<"3.9"',
5860
'python-ulid>=1,<3; python_version>="3.9"',

requirements/pyproject.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ python-dateutil==2.8.2
2222
# time-machine
2323
python-ulid==1.1.0
2424
# via pydantic-extra-types (pyproject.toml)
25+
semver==3.0.2
26+
# via pydantic-extra-types (pyproject.toml)
2527
six==1.16.0
2628
# via python-dateutil
2729
time-machine==2.13.0

tests/test_json_schema.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from pydantic_extra_types.payment import PaymentCardNumber
1919
from pydantic_extra_types.pendulum_dt import DateTime
2020
from pydantic_extra_types.script_code import ISO_15924
21+
from pydantic_extra_types.semantic_version import SemanticVersion
2122
from pydantic_extra_types.ulid import ULID
2223

2324
languages = [lang.alpha_3 for lang in pycountry.languages]
@@ -325,6 +326,15 @@
325326
'type': 'object',
326327
},
327328
),
329+
(
330+
SemanticVersion,
331+
{
332+
'properties': {'x': {'title': 'X', 'type': 'string'}},
333+
'required': ['x'],
334+
'title': 'Model',
335+
'type': 'object',
336+
},
337+
),
328338
],
329339
)
330340
def test_json_schema(cls, expected):

tests/test_semantic_version.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pytest
2+
from pydantic import BaseModel, ValidationError
3+
4+
from pydantic_extra_types.semantic_version import SemanticVersion
5+
6+
7+
@pytest.fixture(scope='module', name='SemanticVersionObject')
8+
def application_object_fixture():
9+
class Application(BaseModel):
10+
version: SemanticVersion
11+
12+
return Application
13+
14+
15+
def test_valid_semantic_version(SemanticVersionObject):
16+
application = SemanticVersionObject(version='1.0.0')
17+
assert application.version
18+
assert application.model_dump() == {'version': '1.0.0'}
19+
20+
21+
def test_invalid_semantic_version(SemanticVersionObject):
22+
with pytest.raises(ValidationError):
23+
SemanticVersionObject(version='Peter Maffay')

0 commit comments

Comments
 (0)