-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add `Latitude`, `Longitude` and `Coordinate` (#76) * feat: add latitude, longitude and coordinate * refactor: apply feedbacks * refactor: apply feedbacks * refactor: delete __init__ functions * fix: coordinate parsing * docs: update coordinate documentation * refactor: use latitude, longitude in schema * 🚧 Some improvements for `Coordinate` type PR (#2) * refactor: delete __init__ functions * 🚧 Some improvements for `Coordinate` type PR * Get tests passing * ✨ Test serialization json schema * ⬆ Upgrade deps in `pyproject.toml` and `requirements/pyproject.txt --------- Co-authored-by: JeanArhancet <[email protected]> Co-authored-by: David Montague <[email protected]> * fix: test and requirements * docs: fix supported format --------- Co-authored-by: Serge Matveenko <[email protected]> Co-authored-by: David Montague <[email protected]> ✨ add ulib type refactor: delete init function
- Loading branch information
1 parent
0c42c50
commit 179393b
Showing
5 changed files
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
""" | ||
The `pydantic_extra_types.ULID` module provides the [`ULID`] data type. | ||
This class depends on the [python-ulid] package, which is a validate by the [ULID-spec](https://github.com/ulid/spec#implementations-in-other-languages). | ||
""" | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
from typing import Any, Union | ||
|
||
from pydantic import GetCoreSchemaHandler | ||
from pydantic._internal import _repr | ||
from pydantic_core import PydanticCustomError, core_schema | ||
|
||
try: | ||
from ulid import ULID as _ULID | ||
except ModuleNotFoundError: # pragma: no cover | ||
raise RuntimeError( | ||
'The `ulid` module requires "python-ulid" to be installed. You can install it with "pip install python-ulid".' | ||
) | ||
|
||
UlidType = Union[str, bytes, int] | ||
|
||
|
||
@dataclass | ||
class ULID(_repr.Representation): | ||
""" | ||
A wrapper around [python-ulid](https://pypi.org/project/python-ulid/) package, which | ||
is a validate by the [ULID-spec](https://github.com/ulid/spec#implementations-in-other-languages). | ||
""" | ||
|
||
ulid: _ULID | ||
|
||
@classmethod | ||
def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: | ||
return core_schema.no_info_wrap_validator_function( | ||
cls._validate_ulid, | ||
core_schema.union_schema( | ||
[ | ||
core_schema.is_instance_schema(_ULID), | ||
core_schema.int_schema(), | ||
core_schema.bytes_schema(), | ||
core_schema.str_schema(), | ||
] | ||
), | ||
) | ||
|
||
@classmethod | ||
def _validate_ulid(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any: | ||
ulid: _ULID | ||
try: | ||
if isinstance(value, int): | ||
ulid = _ULID.from_int(value) | ||
elif isinstance(value, str): | ||
ulid = _ULID.from_str(value) | ||
elif isinstance(value, _ULID): | ||
ulid = value | ||
else: | ||
ulid = _ULID.from_bytes(value) | ||
except ValueError: | ||
raise PydanticCustomError('ulid_format', 'Unrecognized format') | ||
return handler(ulid) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from datetime import datetime, timezone | ||
from typing import Any | ||
|
||
import pytest | ||
from pydantic import BaseModel, ValidationError | ||
|
||
from pydantic_extra_types.ulid import ULID | ||
|
||
try: | ||
from ulid import ULID as _ULID | ||
except ModuleNotFoundError: # pragma: no cover | ||
raise RuntimeError( | ||
'The `ulid` module requires "python-ulid" to be installed. You can install it with "pip install python-ulid".' | ||
) | ||
|
||
|
||
class Something(BaseModel): | ||
ulid: ULID | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'ulid, result, valid', | ||
[ | ||
# Valid ULID for str format | ||
('01BTGNYV6HRNK8K8VKZASZCFPE', '01BTGNYV6HRNK8K8VKZASZCFPE', True), | ||
('01BTGNYV6HRNK8K8VKZASZCFPF', '01BTGNYV6HRNK8K8VKZASZCFPF', True), | ||
# Invalid ULID for str format | ||
('01BTGNYV6HRNK8K8VKZASZCFP', None, False), # Invalid ULID (short length) | ||
('01BTGNYV6HRNK8K8VKZASZCFPEA', None, False), # Invalid ULID (long length) | ||
# Valid ULID for _ULID format | ||
(_ULID.from_str('01BTGNYV6HRNK8K8VKZASZCFPE'), '01BTGNYV6HRNK8K8VKZASZCFPE', True), | ||
(_ULID.from_str('01BTGNYV6HRNK8K8VKZASZCFPF'), '01BTGNYV6HRNK8K8VKZASZCFPF', True), | ||
# Invalid _ULID for bytes format | ||
(b'\x01\xBA\x1E\xB2\x8A\x9F\xFAy\x10\xD5\xA5k\xC8', None, False), # Invalid ULID (short length) | ||
(b'\x01\xBA\x1E\xB2\x8A\x9F\xFAy\x10\xD5\xA5k\xC8\xB6\x00', None, False), # Invalid ULID (long length) | ||
# Valid ULID for int format | ||
(109667145845879622871206540411193812282, '2JG4FVY7N8XS4GFVHPXGJZ8S9T', True), | ||
(109667145845879622871206540411193812283, '2JG4FVY7N8XS4GFVHPXGJZ8S9V', True), | ||
(109667145845879622871206540411193812284, '2JG4FVY7N8XS4GFVHPXGJZ8S9W', True), | ||
], | ||
) | ||
def test_format_for_ulid(ulid: Any, result: Any, valid: bool): | ||
if valid: | ||
assert str(Something(ulid=ulid).ulid) == result | ||
else: | ||
with pytest.raises(ValidationError, match='format'): | ||
Something(ulid=ulid) | ||
|
||
|
||
def test_property_for_ulid(): | ||
ulid = Something(ulid='01BTGNYV6HRNK8K8VKZASZCFPE').ulid | ||
assert ulid.hex == '015ea15f6cd1c56689a373fab3f63ece' | ||
assert ulid == '01BTGNYV6HRNK8K8VKZASZCFPE' | ||
assert ulid.datetime == datetime(2017, 9, 20, 22, 18, 59, 153000, tzinfo=timezone.utc) | ||
assert ulid.timestamp == 1505945939.153 | ||
|
||
|
||
def test_json_schema(): | ||
assert Something.model_json_schema(mode='validation') == { | ||
'properties': { | ||
'ulid': { | ||
'anyOf': [{'type': 'integer'}, {'format': 'binary', 'type': 'string'}, {'type': 'string'}], | ||
'title': 'Ulid', | ||
} | ||
}, | ||
'required': ['ulid'], | ||
'title': 'Something', | ||
'type': 'object', | ||
} | ||
assert Something.model_json_schema(mode='serialization') == { | ||
'properties': { | ||
'ulid': { | ||
'anyOf': [{'type': 'integer'}, {'format': 'binary', 'type': 'string'}, {'type': 'string'}], | ||
'title': 'Ulid', | ||
} | ||
}, | ||
'required': ['ulid'], | ||
'title': 'Something', | ||
'type': 'object', | ||
} |