Skip to content

バリデーション(Pydantic)

モデル分離 の最後で「EmailStrField(max_length=...) でバリデーションできる」と書いた。
ここでは Pydantic のデータバリデーション・ field_validatorcomputed_field を見ていく。


Pydanticによるデータバリデーションの基本

例えば User クラスを普通の class で作って、データのバリデーションもしようとすると、こうなる。めんどくさい……

# 普通の class でバリデーションする場合
class User:
    def __init__(self, name: str, email: str, age: int):
        if not isinstance(name, str):
            raise TypeError("name must be str")
        if len(name) > 255:
            raise ValueError("name must be 255 characters or fewer")

        if not isinstance(age, int):
            raise TypeError("age must be int")
        if not (18 <= age <= 100):
            raise ValueError("age must be between 18 and 100")

        self.name = name
        self.email = email
        self.age = age


alice = User("Alice", "alice@example.com", 30)

print(alice.name)
print(alice.email)
print(alice.age)
Alice
alice@example.com
30

Pydantic を使えば、かなりスッキリする。 EmailStr みたいに import するだけで使えるバリデーション用の型もたくさんある。

# Pydantic を使う場合
from pydantic import BaseModel, EmailStr, Field


class User(BaseModel):
    name: str = Field(max_length=255)
    email: EmailStr
    age: int = Field(ge=18, le=100)


alice_data = {
    "name": "Alice",
    "email": "alice@example.com",
    "age": 30,
}

alice = User(**alice_data)

print(alice.name)
print(alice.email)
print(alice.age)
Alice
alice@example.com
30

EmailStr を使うには

EmailStremail-validator が必要。 fastapi[standard] を入れていれば一緒に入ってるけど、素の Pydantic だけで使うなら uv add "pydantic[email]" で入れる。

例えば alice_dataemailalice-example.com@- に変更)にするとエラーになる。

Traceback (most recent call last):
  ...
pydantic_core._pydantic_core.ValidationError: 1 validation error for User
email
  value is not a valid email address: An email address must have an @-sign. [type=value_error, input_value='alice-example.com', input_type=str]

Pydantic の BaseModel でモデルを作って、それをエンドポイントの引数にしておけば、入ってくるデータのバリデーションができる。


Field validators

例えば下記のようなモデルを定義したとすると、これだとパスワードの長さだけをバリデーションできる。

class UpdatePassword(SQLModel):
    current_password: str = Field(min_length=12, max_length=128)
    new_password: str = Field(min_length=12, max_length=128)

ここにパスワードの複雑さチェック( zxcvbn )も追加したい、となったら field_validator が使える。

zxcvbn をインストールしてから。

uv add zxcvbn

# models.py の UpdatePassword
from pydantic import field_validator
from sqlmodel import SQLModel, Field
from zxcvbn import zxcvbn


def _validate_password_complexity(password: str) -> str:
    """パスワードの複雑さをチェックする"""
    results = zxcvbn(password)
    if results["score"] < 3:
        warning = results["feedback"]["warning"] or "Password is too simple."
        raise ValueError(f"Insufficient security strength: {warning}")
    return password


class UpdatePassword(SQLModel):
    current_password: str = Field(min_length=12, max_length=128)
    new_password: str = Field(min_length=12, max_length=128)

    @field_validator("new_password")
    @classmethod
    def check_strength(cls, v: str) -> str:
        return _validate_password_complexity(v)

Info

@field_validator("new_password") で、 new_password フィールドに対して check_strength を実行する。 score が低いと ValueError を投げるので、 new_password が弱いパスワードだと ValidationError になる。

Note

上の書き方だと、 field_validator は Pydantic の標準バリデーション(型・長さ)の後に実行される(デフォルトで mode="after" )。
cf. https://docs.pydantic.dev/latest/concepts/validators/#field-validators


computed_field decorator

models.py などで定義したクラスのフィールドを加工して返したいときに使える。
下の例は、 first_namelast_name は大文字小文字をそのまま扱うけど、 full_name は大文字に加工して返している。

# computed_field の例
from pydantic import BaseModel, computed_field


class User(BaseModel):
    first_name: str
    last_name: str

    @computed_field
    @property
    def full_name(self) -> str:
        return f"{self.first_name} {self.last_name}".upper()


print(User(first_name="John", last_name="Doe").full_name)
JOHN DOE