🐍
PydanticでEmailStrを拡張する
始めに
pydantic
にはEmailStr
というemailを検証するための拡張クラスがあります。しかし、Emailの仕様としてはUTF-8を許容しているものの、システム的にはASCIIしか許容したくないことがあります。その場合に向けて、EmailStr
を継承してASCII
のみ許容する拡張クラスを作ります。
環境
- Python
- 3.12.7
- Pydantic
- 2.9.2
実装
まずは、ASCII文字だけチェックできるように正規表現を用意します。
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
次のコードでEmailStr
を継承しつつ、自分が追加したvalidationを実行させます。
from typing import Type, Any
import re
from pydantic import EmailStr, validator, GetCoreSchemaHandler
from pydantic_core import core_schema
class CustomEmailStr(EmailStr):
@classmethod
def validate_half_and_full_email(cls, value: str) -> str:
if not re.search(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", value):
raise ValueError("ERROR.EMAIL_VALIDATION")
return value
@classmethod
def __get_pydantic_core_schema__(cls, source_type: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
email_schema = handler.generate_schema(EmailStr)
return core_schema.chain_schema(
[
email_schema,
core_schema.no_info_after_validator_function(
cls.validate_half_and_full_email,
core_schema.str_schema()
),
]
)
このように定義しておくことで、各クラスで別々に追加バリデーションを定義する必要がなくなります。もちろん、処理を追加している分処理が遅くはなっているので、データが登録されうる最小限の箇所でのみ使用し、それ以外はデフォルトのEmailStr
をそのまま使用しても問題ありません。
class _Test(BaseModel):
email: CustomEmailStr
class TestIsValid:
@pytest.mark.parametrize(
"value, expected",
[
("aiueo@example.com", True),
("AIUEO@example.com", True),
("aiueo+0@example.com", False),
("aiueo+あいうえお@example.com", False),
("あいうえお+あいうえお@example.com", False),
],
)
def test_is_valid(self, value, expected):
if expected:
_Test(email=value)
else:
with pytest.raises(ValidationError):
_Test(email=value)
ソースコード
終わりに
単純にValidationをかけるパターンはたくさん出てきますが、特定のクラスを継承するパターンして横展開の工数を減らしたい目的の記事は見つかりませんでした。ぜひ活用してください。
Discussion