概要
特に context (FiledInfoValidator) と組み合わせる場合には注意が必要かなと思います
結論としてはエラーとなりそうなところがあればちゃんと事前に None チェックをしましょうという話です
環境
- Python 3.11.3
- fastapi 0.100.1
- pydantic 2.1.1
サンプルコード
from fastapi import FastAPI
from pydantic import BaseModel, Field, FieldValidationInfo, field_validator
app = FastAPI()
class User(BaseModel):
name: str = Field()
age: int = Field()
@field_validator("name")
@classmethod
def validate_name(cls, v: str, info: FieldValidationInfo):
if info.context is None:
return v
print("validate_name")
if v not in ["hawksnowlog"]:
raise ValueError()
return v
@field_validator("age")
@classmethod
def validate_age_with_context(cls, v: int, info: FieldValidationInfo):
if info.context is None:
return v
if "age_limit" not in info.context or not isinstance(
info.context["age_limit"], int
):
raise ValueError("info.context.age_limit が正しく設定されていません")
# ここがポイント
# 前の validate_name で ValueError になると raise されない
# 更に info.data["name"] には何も設定されずに来るのでここで KeyError が発生してしまう
if info.data["name"] not in ["hawksnowlog"]:
raise ValueError()
# もしやるなら以下のように None チェックしてから info.data["name"] を扱うようにする
if info.data.get("name") is None:
raise ValueError()
if info.data["name"] not in ["hawksnowlog"]:
raise ValueError()
print("validate_age_with_context")
limit = int(info.context["age_limit"])
if v > limit:
raise ValueError(f"age が limit={limit} を超えています")
return v
@app.post("/")
async def test_context(user: User):
# ルーティング時の User 生成時にコールされるバリデーションはどこかで raise すればエラーになる
# model_validate の場合は raise 時にエラーとならずすべてが評価されたあとで raise される
user = User.model_validate(
{"name": "hawksnowlog2", "age": 0}, context={"age_limit": 9}
)
return user
ちょっと解説
基本はコードのコメント部分に書いてあるとおりです
model_validate と context を使ってバリデーションした場合には途中で raise ValueError したものが後ろに回されてまとめて raise されます
なのでバリデーションのコードの中で KeyError など予期せぬエラーが発生した場合にはそこでバリデーションが止まってしまい本当に取得したい ValidationError が取得できなくなってしまいます
なので None 参照や Key 参照をする場合はちゃんと存在するかチェックした上でバリデーションを組む必要があります
0 件のコメント:
コメントを投稿