Skip to content

Commit

Permalink
Add Semantic version type (#195)
Browse files Browse the repository at this point in the history
* Add Semver as a dependency

* Add SemanticVersion type including tests

* Increase test coverage

---------

Co-authored-by: Yasser Tahiri <[email protected]>
  • Loading branch information
nikstuckenbrock and yezz123 authored Jul 2, 2024
1 parent 55c2fcc commit 1cddee1
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 0 deletions.
55 changes: 55 additions & 0 deletions pydantic_extra_types/semantic_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
SemanticVersion definition that is based on the Semantiv Versioning Specification [semver](https://semver.org/).
"""

from typing import Any, Callable

from pydantic import GetJsonSchemaHandler
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema

try:
import semver
except ModuleNotFoundError as e: # pragma: no cover
raise RuntimeError(
'The `semantic_version` module requires "semver" to be installed. You can install it with "pip install semver".'
) from e


class SemanticVersion:
"""
Semantic version based on the official [semver thread](https://python-semver.readthedocs.io/en/latest/advanced/combine-pydantic-and-semver.html).
"""

@classmethod
def __get_pydantic_core_schema__(
cls,
_source_type: Any,
_handler: Callable[[Any], core_schema.CoreSchema],
) -> core_schema.CoreSchema:
def validate_from_str(value: str) -> semver.Version:
return semver.Version.parse(value)

from_str_schema = core_schema.chain_schema(
[
core_schema.str_schema(),
core_schema.no_info_plain_validator_function(validate_from_str),
]
)

return core_schema.json_or_python_schema(
json_schema=from_str_schema,
python_schema=core_schema.union_schema(
[
core_schema.is_instance_schema(semver.Version),
from_str_schema,
]
),
serialization=core_schema.to_string_ser_schema(),
)

@classmethod
def __get_pydantic_json_schema__(
cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
return handler(core_schema.str_schema())
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ dynamic = ['version']
all = [
'phonenumbers>=8,<9',
'pycountry>=23',
'semver>=3.0.2',
'python-ulid>=1,<2; python_version<"3.9"',
'python-ulid>=1,<3; python_version>="3.9"',
'pendulum>=3.0.0,<4.0.0'
]
phonenumbers = ['phonenumbers>=8,<9']
pycountry = ['pycountry>=23']
semver = ['semver>=3.0.2']
python_ulid = [
'python-ulid>=1,<2; python_version<"3.9"',
'python-ulid>=1,<3; python_version>="3.9"',
Expand Down
2 changes: 2 additions & 0 deletions requirements/pyproject.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ python-dateutil==2.8.2
# time-machine
python-ulid==1.1.0
# via pydantic-extra-types (pyproject.toml)
semver==3.0.2
# via pydantic-extra-types (pyproject.toml)
six==1.16.0
# via python-dateutil
time-machine==2.13.0
Expand Down
10 changes: 10 additions & 0 deletions tests/test_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from pydantic_extra_types.payment import PaymentCardNumber
from pydantic_extra_types.pendulum_dt import DateTime
from pydantic_extra_types.script_code import ISO_15924
from pydantic_extra_types.semantic_version import SemanticVersion
from pydantic_extra_types.ulid import ULID

languages = [lang.alpha_3 for lang in pycountry.languages]
Expand Down Expand Up @@ -325,6 +326,15 @@
'type': 'object',
},
),
(
SemanticVersion,
{
'properties': {'x': {'title': 'X', 'type': 'string'}},
'required': ['x'],
'title': 'Model',
'type': 'object',
},
),
],
)
def test_json_schema(cls, expected):
Expand Down
23 changes: 23 additions & 0 deletions tests/test_semantic_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import pytest
from pydantic import BaseModel, ValidationError

from pydantic_extra_types.semantic_version import SemanticVersion


@pytest.fixture(scope='module', name='SemanticVersionObject')
def application_object_fixture():
class Application(BaseModel):
version: SemanticVersion

return Application


def test_valid_semantic_version(SemanticVersionObject):
application = SemanticVersionObject(version='1.0.0')
assert application.version
assert application.model_dump() == {'version': '1.0.0'}


def test_invalid_semantic_version(SemanticVersionObject):
with pytest.raises(ValidationError):
SemanticVersionObject(version='Peter Maffay')

0 comments on commit 1cddee1

Please sign in to comment.