FastAPI で API を開発していて「リクエストを受けたタイミングではバリデーションはせずに別のタイミングでバリデーションをかけたい」といった要件があり、苦戦したので纏めておく。
前提
- Python 3.11.3
- Pydantic 2.6.4
SkipValidation
SkipValidation でバリデーションをスキップすることができるが、任意のタイミングでバリデーションを実行しても Warning は出力されるもののバリデーションエラーとはならずスキップされる。シンプルにバリデーションをしたくないケースにはマッチする。フィールド単位で指定する必要があるため、フィールド数が多かったりネストされたモデルの場合はコード量が増え見通しが悪くなりそう。
from pydantic import BaseModel, SkipValidation class Item(BaseModel): name: SkipValidation[str] price: SkipValidation[float] m1 = Item(name=1, price="foo") print(m1) # -> name=1 price='foo' m1.model_validate(m1.model_dump()) # -> # /path/to/pydantic/functional_validators.py:702: UserWarning: Pydantic serializer warnings: # Expected `str` but got `int` - serialized value may not be as expected # function=lambda v, h: h(v), schema=original_schema # /path/to/pydantic/functional_validators.py:702: UserWarning: Pydantic serializer warnings: # Expected `float` but got `str` - serialized value may not be as expected # function=lambda v, h: h(v), schema=original_schema
model_construct
model_construct メソッドを利用することでやりたいことが実現できた。
from pydantic import BaseModel class Item(BaseModel): name: str price: float m1 = Item.model_construct(name=1, price="foo") print(m1) # -> name=1 price='foo' m1.model_validate(m1.model_dump()) # -> ValidationError
デメリットとしては以下が挙げられる。ドキュメントに記載されている通り、バリデーション済みのデータ、信頼できるデータにのみ利用した方が良い。
- フィールドを指定する際にエディタ補完が効かない
- フィールドで定義していないものを指定できる
- 例:
Item.model_construct(name=1, price="foo", bar="bar")
- 例:
- バリデーションされていないデータのため後続で DB に INSERT するといった場合に型の不整合が発生する可能性がある
meta: MetaModel
といったネストされたモデルがある場合はコード量が増え見通しが悪くなる- 例:
Item.model_construct(name=1, price="foo", meta=Meta.model_construct(author=1))
- 例:
おわりに
Pydanticでバリデーションをスキップして任意のタイミングでバリデーションする方法を紹介した。 この方法はあまり推奨されることではないため、プロダクトの要件に左右されると思うが本当にバリデーションをスキップする必要があるのかを考えたほうが健全。