🆎

【FastAPI】バリデーションエラーメッセージを日本語化

2024/06/08に公開

バリデーションエラーメッセージを日本語化したい

Pydanticでバリデーションエラーメッセージをカスタマイズ

FastAPIでバリデーションエラーメッセージを日本語にしたくて、
FastAPIの公式ドキュメントを漁っていましたが、これといったものが見つからず。。

あきらめかけていましたが、ゆくゆく考えると、バリデーションを行っている分類はPydanticの機能。
ということでPydantic(V2)の公式ドキュメントを見ていくとありました。

Error Handling - Pydantic

きちんと書いてありました。(以下コード抜粋)

from typing import Dict, List

from pydantic_core import ErrorDetails

from pydantic import BaseModel, HttpUrl, ValidationError

CUSTOM_MESSAGES = {
    'int_parsing': 'This is not an integer! 🤦',
    'url_scheme': 'Hey, use the right URL scheme! I wanted {expected_schemes}.',
}

def convert_errors(
    e: ValidationError, custom_messages: Dict[str, str]
) -> List[ErrorDetails]:
    new_errors: List[ErrorDetails] = []
    for error in e.errors():
        custom_message = custom_messages.get(error['type'])
        if custom_message:
            ctx = error.get('ctx')
            error['msg'] = (
                custom_message.format(**ctx) if ctx else custom_message
            )
        new_errors.append(error)
    return new_errors

class Model(BaseModel):
    a: int
    b: HttpUrl

try:
    Model(a='wrong', b='ftp://example.com')
except ValidationError as e:
    errors = convert_errors(e, CUSTOM_MESSAGES)
    print(errors)
    """
    [
        {
            'type': 'int_parsing',
            'loc': ('a',),
            'msg': 'This is not an integer! 🤦',
            'input': 'wrong',
            'url': 'https://errors.pydantic.dev/2/v/int_parsing',
        },
        {
            'type': 'url_scheme',
            'loc': ('b',),
            'msg': "Hey, use the right URL scheme! I wanted 'http' or 'https'.",
            'input': 'ftp://example.com',
            'ctx': {'expected_schemes': "'http' or 'https'"},
            'url': 'https://errors.pydantic.dev/2/v/url_scheme',
        },
    ]
    """

CUSTOM_MESSAGES にエラータイプとカスタムエラーメッセージを定義し、
ユーザー定義関数の convert_errors() でエラータイプをキーに置換している模様。

エラータイプの一覧もありました。

Validation Errors - Pydantic

FastAPIでバリデーションエラーの例外ハンドラをカスタマイズ

あとはFastAPIでバリデーションエラーの例外ハンドラをオーバーライドしてカスタムすればよさそう。

エラーハンドリング - FastAPI

実装例

本を新規登録するAPIを作成。

CreateBookSchemaでリクエストプロパティ titledescription を定義。
title は『必須』、description は『10文字以下』というバリデーションを設定しました。

CUSTOM_MESSAGES には日本語にしたバリデーションエラーメッセージを定義。
convert_errors() 関数でバリデーションエラーメッセージを置換します。

そして、@app.exception_handler(RequestValidationError) でバリデーションエラー時の例外ハンドラをオーバーライド。

from fastapi import FastAPI, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field, ValidationError
from pydantic_core import ErrorDetails

# カスタムエラーメッセージ
CUSTOM_MESSAGES = {
    "string_type": "{input}は必須項目です。",
    "string_too_long": "{input}は{max_length}文字以下で入力してください。",
}


def convert_errors(
    e: ValidationError,
    messages: dict[str, str],
) -> list[ErrorDetails]:
    """バリデーションエラーメッセージをカスタマイズ"""
    new_errors: list[ErrorDetails] = []

    for error in e.errors():
        message = messages.get(error["type"])
        if message:
            ctx = error.get("ctx")
            input = error.get("loc")

            error["msg"] = (
                message.format(input=input[1], **ctx)
                if ctx
                else message.format(input=input[1])
            )
        new_errors.append(error)

    return new_errors


app = FastAPI()


# 例外ハンドラをオーバーライド
@app.exception_handler(RequestValidationError)
def validation_exception_handler(_, e: RequestValidationError):
    # ここでエラーメッセージを日本語に置換
    exc = convert_errors(e=e, messages=CUSTOM_MESSAGES)

    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc}),
    )


class CreateBookSchema(BaseModel):
    title: str
    description: str = Field(max_length=10)


@app.post("/books")
def create_book(request: CreateBookSchema):
    return {"data": request}

バリデーションエラーメッセージが日本語になりました!
バリデーションエラーメッセージ

Discussion