-
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 language code ISO 639-3 and ISO 639-5 and definitions and tests
* added dynamically generated literals based on pycountry * tested all possibilities and errors exhaustively
- Loading branch information
07pepa
committed
Feb 26, 2024
1 parent
f6dba1e
commit 5a0a268
Showing
2 changed files
with
128 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,75 @@ | ||
from typing import Any | ||
|
||
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler | ||
from pydantic_core import PydanticCustomError, core_schema | ||
|
||
try: | ||
import pycountry | ||
except ModuleNotFoundError: # pragma: no cover | ||
raise RuntimeError( | ||
'The `language_code` module requires "pycountry" to be installed.' | ||
' You can install it with "pip install pycountry".' | ||
) | ||
|
||
|
||
class ISO639_3(str): | ||
# noinspection PyUnresolvedReferences | ||
allowed_values_list = [lang.alpha_3 for lang in pycountry.languages] | ||
allowed_values = set(allowed_values_list) | ||
|
||
@classmethod | ||
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> 'ISO639_3': | ||
if __input_value not in cls.allowed_values: | ||
raise PydanticCustomError( | ||
'ISO649_3', 'Invalid ISO 639-3 language code. See https://en.wikipedia.org/wiki/ISO_639-3' | ||
) | ||
return cls(__input_value) | ||
|
||
@classmethod | ||
def __get_pydantic_core_schema__( | ||
cls, _: type[Any], __: GetCoreSchemaHandler | ||
) -> core_schema.AfterValidatorFunctionSchema: | ||
return core_schema.with_info_after_validator_function( | ||
cls._validate, | ||
core_schema.str_schema(min_length=3, max_length=3), | ||
) | ||
|
||
@classmethod | ||
def __get_pydantic_json_schema__( | ||
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler | ||
) -> dict[str, Any]: | ||
json_schema = handler(schema) | ||
json_schema.update({'enum': cls.allowed_values_list}) | ||
return json_schema | ||
|
||
|
||
class ISO639_5(str): | ||
# noinspection PyUnresolvedReferences | ||
allowed_values_list = [lang.alpha_3 for lang in pycountry.language_families] | ||
allowed_values_list.sort() | ||
allowed_values = set(allowed_values_list) | ||
|
||
@classmethod | ||
def _validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> 'ISO639_5': | ||
if __input_value not in cls.allowed_values: | ||
raise PydanticCustomError( | ||
'ISO649_5', 'Invalid ISO 639-5 language code. See https://en.wikipedia.org/wiki/ISO_639-5' | ||
) | ||
return cls(__input_value) | ||
|
||
@classmethod | ||
def __get_pydantic_core_schema__( | ||
cls, _: type[Any], __: GetCoreSchemaHandler | ||
) -> core_schema.AfterValidatorFunctionSchema: | ||
return core_schema.with_info_after_validator_function( | ||
cls._validate, | ||
core_schema.str_schema(min_length=3, max_length=3), | ||
) | ||
|
||
@classmethod | ||
def __get_pydantic_json_schema__( | ||
cls, schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler | ||
) -> dict[str, Any]: | ||
json_schema = handler(schema) | ||
json_schema.update({'enum': cls.allowed_values_list}) | ||
return json_schema |
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,53 @@ | ||
import re | ||
|
||
import pycountry | ||
import pytest | ||
from pydantic import BaseModel, ValidationError | ||
|
||
from pydantic_extra_types import language_code | ||
|
||
|
||
class ISO3CheckingModel(BaseModel): | ||
lang: language_code.ISO639_3 | ||
|
||
|
||
class ISO5CheckingModel(BaseModel): | ||
lang: language_code.ISO639_5 | ||
|
||
|
||
@pytest.mark.parametrize('lang', map(lambda lang: lang.alpha_3, pycountry.languages)) | ||
def test_iso_ISO639_3_code_ok(lang: str): | ||
model = ISO3CheckingModel(lang=lang) | ||
assert model.lang == lang | ||
|
||
|
||
@pytest.mark.parametrize('lang', map(lambda lang: lang.alpha_3, pycountry.language_families)) | ||
def test_iso_639_5_code_ok(lang: str): | ||
model = ISO5CheckingModel(lang=lang) | ||
assert model.lang == lang | ||
|
||
|
||
def test_iso3_language_fail(): | ||
with pytest.raises( | ||
ValidationError, | ||
match=re.escape( | ||
'1 validation error for ISO3CheckingModel\nlang\n ' | ||
'Value error, LOL is not a valid ISO 639-3 language code. ' | ||
'See https://en.wikipedia.org/wiki/ISO_639-3. ' | ||
) | ||
+ '.+', | ||
): | ||
ISO3CheckingModel(lang='LOL') | ||
|
||
|
||
def test_iso5_language_fail(): | ||
with pytest.raises( | ||
ValidationError, | ||
match=re.escape( | ||
'1 validation error for ISO5CheckingModel\nlang\n ' | ||
'Value error, LOL is not a valid ISO 639-5 language code. ' | ||
'See https://en.wikipedia.org/wiki/ISO_639-5. ' | ||
) | ||
+ '.+', | ||
): | ||
ISO5CheckingModel(lang='LOL') |