diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 02:48:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 02:48:08 +0000 |
commit | e6b0b20305ffdd91cc0330cf73233f0172891ebc (patch) | |
tree | 92b41ba8113b4fe462474506be44c18bb64b8c2e /pydantic_extra_types | |
parent | Releasing debian version 2.6.0-2. (diff) | |
download | pydantic-extra-types-e6b0b20305ffdd91cc0330cf73233f0172891ebc.tar.xz pydantic-extra-types-e6b0b20305ffdd91cc0330cf73233f0172891ebc.zip |
Merging upstream version 2.7.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pydantic_extra_types')
-rw-r--r-- | pydantic_extra_types/__init__.py | 2 | ||||
-rw-r--r-- | pydantic_extra_types/color.py | 1 | ||||
-rw-r--r-- | pydantic_extra_types/coordinate.py | 1 | ||||
-rw-r--r-- | pydantic_extra_types/country.py | 1 | ||||
-rw-r--r-- | pydantic_extra_types/currency_code.py | 1 | ||||
-rw-r--r-- | pydantic_extra_types/language_code.py | 212 | ||||
-rw-r--r-- | pydantic_extra_types/pendulum_dt.py | 118 | ||||
-rw-r--r-- | pydantic_extra_types/phone_numbers.py | 1 | ||||
-rw-r--r-- | pydantic_extra_types/routing_number.py | 1 | ||||
-rw-r--r-- | pydantic_extra_types/ulid.py | 1 |
10 files changed, 337 insertions, 2 deletions
diff --git a/pydantic_extra_types/__init__.py b/pydantic_extra_types/__init__.py index f0e5e1e..766ce2d 100644 --- a/pydantic_extra_types/__init__.py +++ b/pydantic_extra_types/__init__.py @@ -1 +1 @@ -__version__ = '2.6.0' +__version__ = '2.7.0' diff --git a/pydantic_extra_types/color.py b/pydantic_extra_types/color.py index 34aa441..8b9eabe 100644 --- a/pydantic_extra_types/color.py +++ b/pydantic_extra_types/color.py @@ -7,6 +7,7 @@ A few colors have multiple names referring to the sames colors, eg. `grey` and ` In these cases the _last_ color when sorted alphabetically takes preferences, eg. `Color((0, 255, 255)).as_named() == 'cyan'` because "cyan" comes after "aqua". """ + from __future__ import annotations import math diff --git a/pydantic_extra_types/coordinate.py b/pydantic_extra_types/coordinate.py index df470d5..10eaa05 100644 --- a/pydantic_extra_types/coordinate.py +++ b/pydantic_extra_types/coordinate.py @@ -3,6 +3,7 @@ The `pydantic_extra_types.coordinate` module provides the [`Latitude`][pydantic_ [`Longitude`][pydantic_extra_types.coordinate.Longitude], and [`Coordinate`][pydantic_extra_types.coordinate.Coordinate] data types. """ + from dataclasses import dataclass from typing import Any, ClassVar, Tuple, Type diff --git a/pydantic_extra_types/country.py b/pydantic_extra_types/country.py index a6d26e2..7af99c7 100644 --- a/pydantic_extra_types/country.py +++ b/pydantic_extra_types/country.py @@ -1,6 +1,7 @@ """ Country definitions that are based on the [ISO 3166](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes). """ + from __future__ import annotations from dataclasses import dataclass diff --git a/pydantic_extra_types/currency_code.py b/pydantic_extra_types/currency_code.py index c19d9bf..fbc0cbd 100644 --- a/pydantic_extra_types/currency_code.py +++ b/pydantic_extra_types/currency_code.py @@ -1,6 +1,7 @@ """ Currency definitions that are based on the [ISO4217](https://en.wikipedia.org/wiki/ISO_4217). """ + from __future__ import annotations from typing import Any diff --git a/pydantic_extra_types/language_code.py b/pydantic_extra_types/language_code.py index 117e877..8c15385 100644 --- a/pydantic_extra_types/language_code.py +++ b/pydantic_extra_types/language_code.py @@ -1,9 +1,12 @@ """ Language definitions that are based on the [ISO 639-3](https://en.wikipedia.org/wiki/ISO_639-3) & [ISO 639-5](https://en.wikipedia.org/wiki/ISO_639-5). """ + from __future__ import annotations -from typing import Any +from dataclasses import dataclass +from functools import lru_cache +from typing import Any, Union from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler from pydantic_core import PydanticCustomError, core_schema @@ -17,6 +20,213 @@ except ModuleNotFoundError: # pragma: no cover ) +@dataclass +class LanguageInfo: + """ + LanguageInfo is a dataclass that contains the language information. + + Args: + alpha2: The language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format. + alpha3: The language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format. + name: The language name. + """ + + alpha2: Union[str, None] + alpha3: str + name: str + + +@lru_cache +def _languages() -> list[LanguageInfo]: + """ + Return a list of LanguageInfo objects containing the language information. + + Returns: + A list of LanguageInfo objects containing the language information. + """ + return [ + LanguageInfo( + alpha2=getattr(language, 'alpha_2', None), + alpha3=language.alpha_3, + name=language.name, + ) + for language in pycountry.languages + ] + + +@lru_cache +def _index_by_alpha2() -> dict[str, LanguageInfo]: + """ + Return a dictionary with the language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format as the key and the LanguageInfo object as the value. + """ + return {language.alpha2: language for language in _languages() if language.alpha2 is not None} + + +@lru_cache +def _index_by_alpha3() -> dict[str, LanguageInfo]: + """ + Return a dictionary with the language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format as the key and the LanguageInfo object as the value. + """ + return {language.alpha3: language for language in _languages()} + + +@lru_cache +def _index_by_name() -> dict[str, LanguageInfo]: + """ + Return a dictionary with the language name as the key and the LanguageInfo object as the value. + """ + return {language.name: language for language in _languages()} + + +class LanguageAlpha2(str): + """LanguageAlpha2 parses languages codes in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) + format. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.language_code import LanguageAlpha2 + + class Movie(BaseModel): + audio_lang: LanguageAlpha2 + subtitles_lang: LanguageAlpha2 + + movie = Movie(audio_lang='de', subtitles_lang='fr') + print(movie) + #> audio_lang='de' subtitles_lang='fr' + ``` + """ + + @classmethod + def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> LanguageAlpha2: + """ + Validate a language code in the ISO 639-1 alpha-2 format from the provided str value. + + Args: + __input_value: The str value to be validated. + _: The Pydantic ValidationInfo. + + Returns: + The validated language code in the ISO 639-1 alpha-2 format. + """ + if __input_value not in _index_by_alpha2(): + raise PydanticCustomError('language_alpha2', 'Invalid language alpha2 code') + return cls(__input_value) + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.AfterValidatorFunctionSchema: + """ + Return a Pydantic CoreSchema with the language code in the ISO 639-1 alpha-2 format validation. + + Args: + source: The source type. + handler: The handler to get the CoreSchema. + + Returns: + A Pydantic CoreSchema with the language code in the ISO 639-1 alpha-2 format validation. + """ + return core_schema.with_info_after_validator_function( + cls._validate, + core_schema.str_schema(to_lower=True), + ) + + @classmethod + def __get_pydantic_json_schema__( + cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> dict[str, Any]: + """ + Return a Pydantic JSON Schema with the language code in the ISO 639-1 alpha-2 format validation. + + Args: + schema: The Pydantic CoreSchema. + handler: The handler to get the JSON Schema. + + Returns: + A Pydantic JSON Schema with the language code in the ISO 639-1 alpha-2 format validation. + """ + json_schema = handler(schema) + json_schema.update({'pattern': r'^\w{2}$'}) + return json_schema + + @property + def alpha3(self) -> str: + """The language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format.""" + return _index_by_alpha2()[self].alpha3 + + @property + def name(self) -> str: + """The language name.""" + return _index_by_alpha2()[self].name + + +class LanguageName(str): + """LanguageName parses languages names listed in the [ISO 639-3 standard](https://en.wikipedia.org/wiki/ISO_639-3) + format. + + ```py + from pydantic import BaseModel + + from pydantic_extra_types.language_code import LanguageName + + class Movie(BaseModel): + audio_lang: LanguageName + subtitles_lang: LanguageName + + movie = Movie(audio_lang='Dutch', subtitles_lang='Mandarin Chinese') + print(movie) + #> audio_lang='Dutch' subtitles_lang='Mandarin Chinese' + ``` + """ + + @classmethod + def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> LanguageName: + """ + Validate a language name from the provided str value. + + Args: + __input_value: The str value to be validated. + _: The Pydantic ValidationInfo. + + Returns: + The validated language name. + """ + if __input_value not in _index_by_name(): + raise PydanticCustomError('language_name', 'Invalid language name') + return cls(__input_value) + + @classmethod + def __get_pydantic_core_schema__( + cls, source: type[Any], handler: GetCoreSchemaHandler + ) -> core_schema.AfterValidatorFunctionSchema: + """ + Return a Pydantic CoreSchema with the language name validation. + + Args: + source: The source type. + handler: The handler to get the CoreSchema. + + Returns: + A Pydantic CoreSchema with the language name validation. + """ + return core_schema.with_info_after_validator_function( + cls._validate, + core_schema.str_schema(), + serialization=core_schema.to_string_ser_schema(), + ) + + @property + def alpha2(self) -> Union[str, None]: + """The language code in the [ISO 639-1 alpha-2](https://en.wikipedia.org/wiki/ISO_639-1) format. Does not exist for all languages.""" + return _index_by_name()[self].alpha2 + + @property + def alpha3(self) -> str: + """The language code in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3) format.""" + return _index_by_name()[self].alpha3 + + class ISO639_3(str): """ISO639_3 parses Language in the [ISO 639-3 alpha-3](https://en.wikipedia.org/wiki/ISO_639-3_alpha-3) format. diff --git a/pydantic_extra_types/pendulum_dt.py b/pydantic_extra_types/pendulum_dt.py index f507779..f3a304f 100644 --- a/pydantic_extra_types/pendulum_dt.py +++ b/pydantic_extra_types/pendulum_dt.py @@ -4,7 +4,9 @@ CoreSchema implementation. This allows Pydantic to validate the DateTime object. """ 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 raise RuntimeError( @@ -72,3 +74,119 @@ class DateTime(_DateTime): except Exception as exc: raise PydanticCustomError('value_error', 'value is not a valid timestamp') from exc return handler(data) + + +class Date(_Date): + """ + A `pendulum.Date` object. At runtime, this type decomposes into pendulum.Date automatically. + This type exists because Pydantic throws a fit on unknown types. + + ```python + from pydantic import BaseModel + from pydantic_extra_types.pendulum_dt import Date + + class test_model(BaseModel): + dt: Date + + print(test_model(dt='2021-01-01')) + + #> test_model(dt=Date(2021, 1, 1)) + ``` + """ + + __slots__: List[str] = [] + + @classmethod + def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + """ + Return a Pydantic CoreSchema with the Date validation + + Args: + source: The source type to be converted. + handler: The handler to get the CoreSchema. + + Returns: + A Pydantic CoreSchema with the Date validation. + """ + return core_schema.no_info_wrap_validator_function(cls._validate, core_schema.date_schema()) + + @classmethod + def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any: + """ + Validate the date object and return it. + + Args: + value: The value to validate. + handler: The handler to get the CoreSchema. + + Returns: + The validated value or raises a PydanticCustomError. + """ + # if we are passed an existing instance, pass it straight through. + if isinstance(value, _Date): + return handler(value) + + # otherwise, parse it. + try: + data = parse(value) + except Exception as exc: + raise PydanticCustomError('value_error', 'value is not a valid date') from exc + return handler(data) + + +class Duration(_Duration): + """ + A `pendulum.Duration` object. At runtime, this type decomposes into pendulum.Duration automatically. + This type exists because Pydantic throws a fit on unknown types. + + ```python + from pydantic import BaseModel + from pydantic_extra_types.pendulum_dt import Duration + + class test_model(BaseModel): + delta_t: Duration + + print(test_model(delta_t='P1DT25H')) + + #> test_model(delta_t=Duration(days=2, hours=1)) + ``` + """ + + __slots__: List[str] = [] + + @classmethod + def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + """ + Return a Pydantic CoreSchema with the Duration validation + + Args: + source: The source type to be converted. + handler: The handler to get the CoreSchema. + + Returns: + A Pydantic CoreSchema with the Duration validation. + """ + return core_schema.no_info_wrap_validator_function(cls._validate, core_schema.timedelta_schema()) + + @classmethod + def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any: + """ + Validate the Duration object and return it. + + Args: + value: The value to validate. + handler: The handler to get the CoreSchema. + + Returns: + The validated value or raises a PydanticCustomError. + """ + # if we are passed an existing instance, pass it straight through. + if isinstance(value, _Duration): + return handler(value) + + # otherwise, parse it. + try: + data = parse(value) + except Exception as exc: + raise PydanticCustomError('value_error', 'value is not a valid duration') from exc + return handler(data) diff --git a/pydantic_extra_types/phone_numbers.py b/pydantic_extra_types/phone_numbers.py index 7acaa89..cf03417 100644 --- a/pydantic_extra_types/phone_numbers.py +++ b/pydantic_extra_types/phone_numbers.py @@ -4,6 +4,7 @@ The `pydantic_extra_types.phone_numbers` module provides the This class depends on the [phonenumbers] package, which is a Python port of Google's [libphonenumber]. """ + from __future__ import annotations from typing import Any, Callable, ClassVar, Generator diff --git a/pydantic_extra_types/routing_number.py b/pydantic_extra_types/routing_number.py index 22ea6e8..b4d53a8 100644 --- a/pydantic_extra_types/routing_number.py +++ b/pydantic_extra_types/routing_number.py @@ -2,6 +2,7 @@ The `pydantic_extra_types.routing_number` module provides the [`ABARoutingNumber`][pydantic_extra_types.routing_number.ABARoutingNumber] data type. """ + from typing import Any, ClassVar, Type from pydantic import GetCoreSchemaHandler diff --git a/pydantic_extra_types/ulid.py b/pydantic_extra_types/ulid.py index d2bf650..5891f9f 100644 --- a/pydantic_extra_types/ulid.py +++ b/pydantic_extra_types/ulid.py @@ -3,6 +3,7 @@ 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 |