Adding upstream version 2.8.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
b7dc1a1abe
commit
044c82d56c
18 changed files with 341 additions and 77 deletions
21
HISTORY.md
21
HISTORY.md
|
@ -2,6 +2,27 @@
|
|||
|
||||
## Latest Changes
|
||||
|
||||
## 2.8.0
|
||||
|
||||
### Refactor
|
||||
|
||||
* ♻️ refactor some functions & minor changes. [#180](https://github.com/pydantic/pydantic-extra-types/pull/180) by [@yezz123](https://github.com/yezz123)
|
||||
|
||||
### Internal
|
||||
|
||||
* Allow requiring extra dependencies. [#178](https://github.com/pydantic/pydantic-extra-types/pull/178) by [@yezz123](https://github.com/yezz123)
|
||||
|
||||
### Types
|
||||
|
||||
* Add ISO 15924 and tests. [#174](https://github.com/pydantic/pydantic-extra-types/pull/174) by [@07pepa](https://github.com/07pepa)
|
||||
* add native datetime to pendulum_dt.py. [#176](https://github.com/pydantic/pydantic-extra-types/pull/176) by [@07pepa](https://github.com/07pepa)
|
||||
* add hash and eq to phone_numbers. [#177](https://github.com/pydantic/pydantic-extra-types/pull/177) by [@07pepa](https://github.com/07pepa)
|
||||
|
||||
### Dependencies
|
||||
|
||||
* ⬆ Bump the python-packages group with 5 updates. PR [#179](https://github.com/pydantic/pydantic-extra-types/pull/179) by @dependabot
|
||||
* ⬆ Bump the python-packages group with 4 updates. PR [#171](https://github.com/pydantic/pydantic-extra-types/pull/171) by @dependabot
|
||||
|
||||
## 2.7.0
|
||||
|
||||
* 🔥 Remove latest-changes workflow. PR [#165](https://github.com/pydantic/pydantic-extra-types/pull/165) by [yezz123](https://github.com/yezz123)
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = '2.7.0'
|
||||
__version__ = '2.8.0'
|
||||
|
|
|
@ -121,17 +121,16 @@ class Color(_repr.Representation):
|
|||
Raises:
|
||||
ValueError: When no named color is found and fallback is `False`.
|
||||
"""
|
||||
if self._rgba.alpha is None:
|
||||
rgb = cast(Tuple[int, int, int], self.as_rgb_tuple())
|
||||
try:
|
||||
return COLORS_BY_VALUE[rgb]
|
||||
except KeyError as e:
|
||||
if fallback:
|
||||
return self.as_hex()
|
||||
else:
|
||||
raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e
|
||||
else:
|
||||
if self._rgba.alpha is not None:
|
||||
return self.as_hex()
|
||||
rgb = cast(Tuple[int, int, int], self.as_rgb_tuple())
|
||||
try:
|
||||
return COLORS_BY_VALUE[rgb]
|
||||
except KeyError as e:
|
||||
if fallback:
|
||||
return self.as_hex()
|
||||
else:
|
||||
raise ValueError('no named color found, use fallback=True, as_hex() or as_rgb()') from e
|
||||
|
||||
def as_hex(self, format: Literal['short', 'long'] = 'short') -> str:
|
||||
"""Returns the hexadecimal representation of the color.
|
||||
|
@ -149,7 +148,7 @@ class Color(_repr.Representation):
|
|||
as_hex = ''.join(f'{v:02x}' for v in values)
|
||||
if format == 'short' and all(c in repeat_colors for c in values):
|
||||
as_hex = ''.join(as_hex[c] for c in range(0, len(as_hex), 2))
|
||||
return '#' + as_hex
|
||||
return f'#{as_hex}'
|
||||
|
||||
def as_rgb(self) -> str:
|
||||
"""
|
||||
|
@ -179,16 +178,10 @@ class Color(_repr.Representation):
|
|||
If alpha is included, it is in the range 0 to 1.
|
||||
"""
|
||||
r, g, b = (float_to_255(c) for c in self._rgba[:3])
|
||||
if alpha is None:
|
||||
if self._rgba.alpha is None:
|
||||
return r, g, b
|
||||
else:
|
||||
return r, g, b, self._alpha_float()
|
||||
elif alpha:
|
||||
return r, g, b, self._alpha_float()
|
||||
else:
|
||||
# alpha is False
|
||||
if alpha is None and self._rgba.alpha is None or alpha is not None and not alpha:
|
||||
return r, g, b
|
||||
else:
|
||||
return r, g, b, self._alpha_float()
|
||||
|
||||
def as_hsl(self) -> str:
|
||||
"""
|
||||
|
@ -225,11 +218,7 @@ class Color(_repr.Representation):
|
|||
return h, s, l
|
||||
else:
|
||||
return h, s, l, self._alpha_float()
|
||||
if alpha:
|
||||
return h, s, l, self._alpha_float()
|
||||
else:
|
||||
# alpha is False
|
||||
return h, s, l
|
||||
return (h, s, l, self._alpha_float()) if alpha else (h, s, l)
|
||||
|
||||
def _alpha_float(self) -> float:
|
||||
return 1 if self._rgba.alpha is None else self._rgba.alpha
|
||||
|
@ -315,20 +304,14 @@ def parse_str(value: str) -> RGBA:
|
|||
if m:
|
||||
*rgb, a = m.groups()
|
||||
r, g, b = (int(v * 2, 16) for v in rgb)
|
||||
if a:
|
||||
alpha: float | None = int(a * 2, 16) / 255
|
||||
else:
|
||||
alpha = None
|
||||
alpha = int(a * 2, 16) / 255 if a else None
|
||||
return ints_to_rgba(r, g, b, alpha)
|
||||
|
||||
m = re.fullmatch(r_hex_long, value_lower)
|
||||
if m:
|
||||
*rgb, a = m.groups()
|
||||
r, g, b = (int(v, 16) for v in rgb)
|
||||
if a:
|
||||
alpha = int(a, 16) / 255
|
||||
else:
|
||||
alpha = None
|
||||
alpha = int(a, 16) / 255 if a else None
|
||||
return ints_to_rgba(r, g, b, alpha)
|
||||
|
||||
m = re.fullmatch(r_rgb, value_lower) or re.fullmatch(r_rgb_v4_style, value_lower)
|
||||
|
@ -390,11 +373,11 @@ def parse_color_value(value: int | str, max_val: int = 255) -> float:
|
|||
"""
|
||||
try:
|
||||
color = float(value)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
raise PydanticCustomError(
|
||||
'color_error',
|
||||
'value is not a valid color: color values must be a valid number',
|
||||
)
|
||||
) from e
|
||||
if 0 <= color <= max_val:
|
||||
return color / max_val
|
||||
else:
|
||||
|
@ -425,11 +408,11 @@ def parse_float_alpha(value: None | str | float | int) -> float | None:
|
|||
alpha = float(value[:-1]) / 100
|
||||
else:
|
||||
alpha = float(value)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
raise PydanticCustomError(
|
||||
'color_error',
|
||||
'value is not a valid color: alpha values must be a valid float',
|
||||
)
|
||||
) from e
|
||||
|
||||
if math.isclose(alpha, 1):
|
||||
return None
|
||||
|
@ -465,7 +448,7 @@ def parse_hsl(h: str, h_units: str, sat: str, light: str, alpha: float | None =
|
|||
h_value = h_value % rads / rads
|
||||
else:
|
||||
# turns
|
||||
h_value = h_value % 1
|
||||
h_value %= 1
|
||||
|
||||
r, g, b = hls_to_rgb(h_value, l_value, s_value)
|
||||
return RGBA(r, g, b, parse_float_alpha(alpha))
|
||||
|
|
|
@ -123,18 +123,16 @@ class Coordinate(_repr.Representation):
|
|||
return value
|
||||
try:
|
||||
value = tuple(float(x) for x in value.split(','))
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
raise PydanticCustomError(
|
||||
'coordinate_error',
|
||||
'value is not a valid coordinate: string is not recognized as a valid coordinate',
|
||||
)
|
||||
) from e
|
||||
return ArgsKwargs(args=value)
|
||||
|
||||
@classmethod
|
||||
def _parse_tuple(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any:
|
||||
if not isinstance(value, tuple):
|
||||
return value
|
||||
return ArgsKwargs(args=handler(value))
|
||||
return ArgsKwargs(args=handler(value)) if isinstance(value, tuple) else value
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.latitude},{self.longitude}'
|
||||
|
|
|
@ -13,10 +13,10 @@ from pydantic_core import PydanticCustomError, core_schema
|
|||
|
||||
try:
|
||||
import pycountry
|
||||
except ModuleNotFoundError: # pragma: no cover
|
||||
except ModuleNotFoundError as e: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
'The `country` module requires "pycountry" to be installed. You can install it with "pip install pycountry".'
|
||||
)
|
||||
) from e
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
@ -11,11 +11,11 @@ from pydantic_core import PydanticCustomError, core_schema
|
|||
|
||||
try:
|
||||
import pycountry
|
||||
except ModuleNotFoundError: # pragma: no cover
|
||||
except ModuleNotFoundError as e: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
'The `currency_code` module requires "pycountry" to be installed. You can install it with "pip install '
|
||||
'pycountry".'
|
||||
)
|
||||
) from e
|
||||
|
||||
# List of codes that should not be usually used within regular transactions
|
||||
_CODES_FOR_BONDS_METAL_TESTING = {
|
||||
|
|
|
@ -13,11 +13,11 @@ from pydantic_core import PydanticCustomError, core_schema
|
|||
|
||||
try:
|
||||
import pycountry
|
||||
except ModuleNotFoundError: # pragma: no cover
|
||||
except ModuleNotFoundError as e: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
'The `language_code` module requires "pycountry" to be installed.'
|
||||
' You can install it with "pip install pycountry".'
|
||||
)
|
||||
) from e
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
@ -3,15 +3,18 @@ Native Pendulum DateTime object implementation. This is a copy of the Pendulum D
|
|||
CoreSchema implementation. This allows Pydantic to validate the DateTime object.
|
||||
"""
|
||||
|
||||
import pendulum
|
||||
|
||||
try:
|
||||
from pendulum import Date as _Date
|
||||
from pendulum import DateTime as _DateTime
|
||||
from pendulum import Duration as _Duration
|
||||
from pendulum import parse
|
||||
except ModuleNotFoundError: # pragma: no cover
|
||||
except ModuleNotFoundError as e: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
'The `pendulum_dt` module requires "pendulum" to be installed. You can install it with "pip install pendulum".'
|
||||
)
|
||||
) from e
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Any, List, Type
|
||||
|
||||
from pydantic import GetCoreSchemaHandler
|
||||
|
@ -68,6 +71,9 @@ class DateTime(_DateTime):
|
|||
if isinstance(value, _DateTime):
|
||||
return handler(value)
|
||||
|
||||
if isinstance(value, datetime):
|
||||
return handler(DateTime.instance(value))
|
||||
|
||||
# otherwise, parse it.
|
||||
try:
|
||||
data = parse(value)
|
||||
|
@ -126,6 +132,9 @@ class Date(_Date):
|
|||
if isinstance(value, _Date):
|
||||
return handler(value)
|
||||
|
||||
if isinstance(value, date):
|
||||
return handler(pendulum.instance(value))
|
||||
|
||||
# otherwise, parse it.
|
||||
try:
|
||||
data = parse(value)
|
||||
|
@ -184,6 +193,9 @@ class Duration(_Duration):
|
|||
if isinstance(value, _Duration):
|
||||
return handler(value)
|
||||
|
||||
if isinstance(value, timedelta):
|
||||
return handler(_Duration(seconds=value.total_seconds()))
|
||||
|
||||
# otherwise, parse it.
|
||||
try:
|
||||
data = parse(value)
|
||||
|
|
|
@ -14,10 +14,10 @@ from pydantic_core import PydanticCustomError, core_schema
|
|||
|
||||
try:
|
||||
import phonenumbers
|
||||
except ModuleNotFoundError: # pragma: no cover
|
||||
except ModuleNotFoundError as e: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
'`PhoneNumber` requires "phonenumbers" to be installed. You can install it with "pip install phonenumbers"'
|
||||
)
|
||||
) from e
|
||||
|
||||
GeneratorCallableStr = Generator[Callable[..., str], None, None]
|
||||
|
||||
|
@ -67,3 +67,9 @@ class PhoneNumber(str):
|
|||
raise PydanticCustomError('value_error', 'value is not a valid phone number')
|
||||
|
||||
return phonenumbers.format_number(parsed_number, getattr(phonenumbers.PhoneNumberFormat, cls.phone_format))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
return super().__eq__(other)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return super().__hash__()
|
||||
|
|
100
pydantic_extra_types/script_code.py
Normal file
100
pydantic_extra_types/script_code.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
"""
|
||||
script definitions that are based on the [ISO 15924](https://en.wikipedia.org/wiki/ISO_15924)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
|
||||
from pydantic_core import PydanticCustomError, core_schema
|
||||
|
||||
try:
|
||||
import pycountry
|
||||
except ModuleNotFoundError as e: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
'The `script_code` module requires "pycountry" to be installed.'
|
||||
' You can install it with "pip install pycountry".'
|
||||
) from e
|
||||
|
||||
|
||||
class ISO_15924(str):
|
||||
"""ISO_15924 parses script in the [ISO 15924](https://en.wikipedia.org/wiki/ISO_15924)
|
||||
format.
|
||||
|
||||
```py
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pydantic_extra_types.language_code import ISO_15924
|
||||
|
||||
class Script(BaseModel):
|
||||
alpha_4: ISO_15924
|
||||
|
||||
script = Script(alpha_4='Java')
|
||||
print(lang)
|
||||
# > script='Java'
|
||||
```
|
||||
"""
|
||||
|
||||
allowed_values_list = [script.alpha_4 for script in pycountry.scripts]
|
||||
allowed_values = set(allowed_values_list)
|
||||
|
||||
@classmethod
|
||||
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> ISO_15924:
|
||||
"""
|
||||
Validate a ISO 15924 language code from the provided str value.
|
||||
|
||||
Args:
|
||||
__input_value: The str value to be validated.
|
||||
_: The Pydantic ValidationInfo.
|
||||
|
||||
Returns:
|
||||
The validated ISO 15924 script code.
|
||||
|
||||
Raises:
|
||||
PydanticCustomError: If the ISO 15924 script code is not valid.
|
||||
"""
|
||||
if __input_value not in cls.allowed_values:
|
||||
raise PydanticCustomError(
|
||||
'ISO_15924', 'Invalid ISO 15924 script code. See https://en.wikipedia.org/wiki/ISO_15924'
|
||||
)
|
||||
return cls(__input_value)
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(
|
||||
cls, _: type[Any], __: GetCoreSchemaHandler
|
||||
) -> core_schema.AfterValidatorFunctionSchema:
|
||||
"""
|
||||
Return a Pydantic CoreSchema with the ISO 639-3 language code validation.
|
||||
|
||||
Args:
|
||||
_: The source type.
|
||||
__: The handler to get the CoreSchema.
|
||||
|
||||
Returns:
|
||||
A Pydantic CoreSchema with the ISO 639-3 language code validation.
|
||||
|
||||
"""
|
||||
return core_schema.with_info_after_validator_function(
|
||||
cls._validate,
|
||||
core_schema.str_schema(min_length=4, max_length=4),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_json_schema__(
|
||||
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Return a Pydantic JSON Schema with the ISO 639-3 language code validation.
|
||||
|
||||
Args:
|
||||
schema: The Pydantic CoreSchema.
|
||||
handler: The handler to get the JSON Schema.
|
||||
|
||||
Returns:
|
||||
A Pydantic JSON Schema with the ISO 639-3 language code validation.
|
||||
|
||||
"""
|
||||
json_schema = handler(schema)
|
||||
json_schema.update({'enum': cls.allowed_values_list})
|
||||
return json_schema
|
|
@ -15,10 +15,10 @@ from pydantic_core import PydanticCustomError, core_schema
|
|||
|
||||
try:
|
||||
from ulid import ULID as _ULID
|
||||
except ModuleNotFoundError: # pragma: no cover
|
||||
except ModuleNotFoundError as e: # pragma: no cover
|
||||
raise RuntimeError(
|
||||
'The `ulid` module requires "python-ulid" to be installed. You can install it with "pip install python-ulid".'
|
||||
)
|
||||
) from e
|
||||
|
||||
UlidType = Union[str, bytes, int]
|
||||
|
||||
|
@ -58,6 +58,6 @@ class ULID(_repr.Representation):
|
|||
ulid = value
|
||||
else:
|
||||
ulid = _ULID.from_bytes(value)
|
||||
except ValueError:
|
||||
raise PydanticCustomError('ulid_format', 'Unrecognized format')
|
||||
except ValueError as e:
|
||||
raise PydanticCustomError('ulid_format', 'Unrecognized format') from e
|
||||
return handler(ulid)
|
||||
|
|
|
@ -51,6 +51,13 @@ all = [
|
|||
'python-ulid>=1,<3; python_version>="3.9"',
|
||||
'pendulum>=3.0.0,<4.0.0'
|
||||
]
|
||||
phonenumbers = ['phonenumbers>=8,<9']
|
||||
pycountry = ['pycountry>=23']
|
||||
python_ulid = [
|
||||
'python-ulid>=1,<2; python_version<"3.9"',
|
||||
'python-ulid>=1,<3; python_version>="3.9"',
|
||||
]
|
||||
pendulum = ['pendulum>=3.0.0,<4.0.0']
|
||||
|
||||
[project.urls]
|
||||
Homepage = 'https://github.com/pydantic/pydantic-extra-types'
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# pip-compile --no-emit-index-url --output-file=requirements/linting.txt requirements/linting.in
|
||||
#
|
||||
annotated-types==0.6.0
|
||||
annotated-types==0.7.0
|
||||
# via -r requirements/linting.in
|
||||
cfgv==3.4.0
|
||||
# via pre-commit
|
||||
|
@ -14,7 +14,7 @@ filelock==3.13.1
|
|||
# via virtualenv
|
||||
identify==2.5.35
|
||||
# via pre-commit
|
||||
mypy==1.9.0
|
||||
mypy==1.10.0
|
||||
# via -r requirements/linting.in
|
||||
mypy-extensions==1.0.0
|
||||
# via mypy
|
||||
|
@ -22,11 +22,11 @@ nodeenv==1.8.0
|
|||
# via pre-commit
|
||||
platformdirs==4.2.0
|
||||
# via virtualenv
|
||||
pre-commit==3.7.0
|
||||
pre-commit==3.7.1
|
||||
# via -r requirements/linting.in
|
||||
pyyaml==6.0.1
|
||||
# via pre-commit
|
||||
ruff==0.3.5
|
||||
ruff==0.4.7
|
||||
# via -r requirements/linting.in
|
||||
typing-extensions==4.10.0
|
||||
# via mypy
|
||||
|
|
|
@ -10,7 +10,7 @@ charset-normalizer==3.3.2
|
|||
# via requests
|
||||
codecov==2.1.13
|
||||
# via -r requirements/testing.in
|
||||
coverage[toml]==7.4.4
|
||||
coverage[toml]==7.5.3
|
||||
# via
|
||||
# -r requirements/testing.in
|
||||
# codecov
|
||||
|
@ -27,11 +27,11 @@ mdurl==0.1.2
|
|||
# via markdown-it-py
|
||||
packaging==23.2
|
||||
# via pytest
|
||||
pluggy==1.4.0
|
||||
pluggy==1.5.0
|
||||
# via pytest
|
||||
pygments==2.17.2
|
||||
# via rich
|
||||
pytest==8.1.1
|
||||
pytest==8.2.1
|
||||
# via
|
||||
# -r requirements/testing.in
|
||||
# pytest-cov
|
||||
|
|
|
@ -17,6 +17,7 @@ from pydantic_extra_types.language_code import ISO639_3, ISO639_5, LanguageAlpha
|
|||
from pydantic_extra_types.mac_address import MacAddress
|
||||
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.ulid import ULID
|
||||
|
||||
languages = [lang.alpha_3 for lang in pycountry.languages]
|
||||
|
@ -32,6 +33,8 @@ everyday_currencies = [
|
|||
if currency.alpha_3 not in pydantic_extra_types.currency_code._CODES_FOR_BONDS_METAL_TESTING
|
||||
]
|
||||
|
||||
scripts = [script.alpha_4 for script in pycountry.scripts]
|
||||
|
||||
everyday_currencies.sort()
|
||||
|
||||
|
||||
|
@ -305,6 +308,23 @@ everyday_currencies.sort()
|
|||
'type': 'object',
|
||||
},
|
||||
),
|
||||
(
|
||||
ISO_15924,
|
||||
{
|
||||
'properties': {
|
||||
'x': {
|
||||
'title': 'X',
|
||||
'type': 'string',
|
||||
'enum': scripts,
|
||||
'maxLength': 4,
|
||||
'minLength': 4,
|
||||
}
|
||||
},
|
||||
'required': ['x'],
|
||||
'title': 'Model',
|
||||
'type': 'object',
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_json_schema(cls, expected):
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
from datetime import date, datetime, timedelta
|
||||
from datetime import timezone as tz
|
||||
|
||||
import pendulum
|
||||
import pytest
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from pydantic_extra_types.pendulum_dt import Date, DateTime, Duration
|
||||
|
||||
UTC = tz.utc
|
||||
|
||||
|
||||
class DtModel(BaseModel):
|
||||
dt: DateTime
|
||||
|
@ -17,32 +22,77 @@ class DurationModel(BaseModel):
|
|||
delta_t: Duration
|
||||
|
||||
|
||||
def test_pendulum_dt_existing_instance():
|
||||
@pytest.mark.parametrize(
|
||||
'instance',
|
||||
[
|
||||
pendulum.now(),
|
||||
datetime.now(),
|
||||
datetime.now(UTC),
|
||||
],
|
||||
)
|
||||
def test_existing_instance(instance):
|
||||
"""
|
||||
Verifies that constructing a model with an existing pendulum dt doesn't throw.
|
||||
"""
|
||||
now = pendulum.now()
|
||||
model = DtModel(dt=now)
|
||||
assert model.dt == now
|
||||
model = DtModel(dt=instance)
|
||||
if isinstance(instance, datetime):
|
||||
assert model.dt == pendulum.instance(instance)
|
||||
if instance.tzinfo is None and isinstance(instance, datetime):
|
||||
instance = model.dt.replace(tzinfo=UTC) # pendulum defaults to UTC
|
||||
dt = model.dt
|
||||
else:
|
||||
assert model.dt == instance
|
||||
dt = model.dt
|
||||
|
||||
assert dt.day == instance.day
|
||||
assert dt.month == instance.month
|
||||
assert dt.year == instance.year
|
||||
assert dt.hour == instance.hour
|
||||
assert dt.minute == instance.minute
|
||||
assert dt.second == instance.second
|
||||
assert dt.microsecond == instance.microsecond
|
||||
if dt.tzinfo != instance.tzinfo:
|
||||
assert dt.tzinfo.utcoffset(dt) == instance.tzinfo.utcoffset(instance)
|
||||
|
||||
|
||||
def test_pendulum_date_existing_instance():
|
||||
@pytest.mark.parametrize(
|
||||
'instance',
|
||||
[
|
||||
pendulum.today(),
|
||||
date.today(),
|
||||
],
|
||||
)
|
||||
def test_pendulum_date_existing_instance(instance):
|
||||
"""
|
||||
Verifies that constructing a model with an existing pendulum date doesn't throw.
|
||||
"""
|
||||
today = pendulum.today().date()
|
||||
model = DateModel(d=today)
|
||||
assert model.d == today
|
||||
model = DateModel(d=instance)
|
||||
if isinstance(instance, datetime):
|
||||
assert model.d == pendulum.instance(instance).date()
|
||||
else:
|
||||
assert model.d == instance
|
||||
d = model.d
|
||||
assert d.day == instance.day
|
||||
assert d.month == instance.month
|
||||
assert d.year == instance.year
|
||||
|
||||
|
||||
def test_pendulum_duration_existing_instance():
|
||||
@pytest.mark.parametrize(
|
||||
'instance',
|
||||
[
|
||||
pendulum.duration(days=42, hours=13, minutes=37),
|
||||
pendulum.duration(days=-42, hours=13, minutes=37),
|
||||
timedelta(days=42, hours=13, minutes=37),
|
||||
timedelta(days=-42, hours=13, minutes=37),
|
||||
],
|
||||
)
|
||||
def test_duration_timedelta__existing_instance(instance):
|
||||
"""
|
||||
Verifies that constructing a model with an existing pendulum duration doesn't throw.
|
||||
"""
|
||||
delta_t = pendulum.duration(days=42, hours=13, minutes=37)
|
||||
model = DurationModel(delta_t=delta_t)
|
||||
model = DurationModel(delta_t=instance)
|
||||
|
||||
assert model.delta_t.total_seconds() == delta_t.total_seconds()
|
||||
assert model.delta_t.total_seconds() == instance.total_seconds()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -52,6 +52,20 @@ def test_parsed_but_not_a_valid_number() -> None:
|
|||
Something(phone_number='+1 555-1212')
|
||||
|
||||
|
||||
def test_hashes() -> None:
|
||||
assert hash(PhoneNumber('555-1212')) == hash(PhoneNumber('555-1212'))
|
||||
assert hash(PhoneNumber('555-1212')) == hash('555-1212')
|
||||
assert hash(PhoneNumber('555-1212')) != hash('555-1213')
|
||||
assert hash(PhoneNumber('555-1212')) != hash(PhoneNumber('555-1213'))
|
||||
|
||||
|
||||
def test_eq() -> None:
|
||||
assert PhoneNumber('555-1212') == PhoneNumber('555-1212')
|
||||
assert PhoneNumber('555-1212') == '555-1212'
|
||||
assert PhoneNumber('555-1212') != '555-1213'
|
||||
assert PhoneNumber('555-1212') != PhoneNumber('555-1213')
|
||||
|
||||
|
||||
def test_json_schema() -> None:
|
||||
assert Something.model_json_schema() == {
|
||||
'title': 'Something',
|
||||
|
|
53
tests/test_scripts.py
Normal file
53
tests/test_scripts.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
import re
|
||||
|
||||
import pycountry
|
||||
import pytest
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from pydantic_extra_types.script_code import ISO_15924
|
||||
|
||||
|
||||
class ScriptCheck(BaseModel):
|
||||
script: ISO_15924
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script', map(lambda lang: lang.alpha_4, pycountry.scripts))
|
||||
def test_ISO_15924_code_ok(script: str):
|
||||
model = ScriptCheck(script=script)
|
||||
assert model.script == script
|
||||
assert str(model.script) == script
|
||||
assert model.model_dump() == {'script': script} # test serialization
|
||||
|
||||
|
||||
def test_ISO_15924_code_fail_not_enought_letters():
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match=re.escape(
|
||||
'1 validation error for ScriptCheck\nscript\n '
|
||||
"String should have at least 4 characters [type=string_too_short, input_value='X', input_type=str]\n"
|
||||
),
|
||||
):
|
||||
ScriptCheck(script='X')
|
||||
|
||||
|
||||
def test_ISO_15924_code_fail_too_much_letters():
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match=re.escape(
|
||||
'1 validation error for ScriptCheck\nscript\n '
|
||||
"String should have at most 4 characters [type=string_too_long, input_value='Klingon', input_type=str]"
|
||||
),
|
||||
):
|
||||
ScriptCheck(script='Klingon')
|
||||
|
||||
|
||||
def test_ISO_15924_code_fail_not_existing():
|
||||
with pytest.raises(
|
||||
ValidationError,
|
||||
match=re.escape(
|
||||
'1 validation error for ScriptCheck\nscript\n '
|
||||
'Invalid ISO 15924 script code. See https://en.wikipedia.org/wiki/ISO_15924 '
|
||||
"[type=ISO_15924, input_value='Klin', input_type=str]"
|
||||
),
|
||||
):
|
||||
ScriptCheck(script='Klin')
|
Loading…
Add table
Reference in a new issue