60
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FastAPIにおけるPydanticを使ったバリデーションのまとめ

Posted at

概要

FastAPIではPydanticというライブラリを利用してモデルスキーマとバリデーションを宣言的に実装できるようになっている。
ここではその具体的な方法を記述する。

確認したバージョンは以下の通り。

  • FastAPI: 0.68.1
  • Pydantic: 1.8.2

使い方

モデルの記述と型チェック

モデルの定義

from pydantic import BaseModel

class Hoge(BaseModel):
    id: int
    name: str

のように書けばHogeモデルが作成される。
Hogeモデルは整数(int)のidが必ず存在し、文字列(str)のnameが必ず存在する。この条件を満たさない場合、バリデーションエラーとなる。

型はPythonの型ヒント(type hints)を使って記述する。

一般的なものを以下に示す。

説明 JSON schema type
None Noneそのもの null
bool 真偽値 boolean
int 整数 number
float 浮動小数点数 number
str 文字列 string
List リスト array
Tuple タプル array
Set セット array(uniqueitems=true となる)
Dict 辞書 object

また、次のものはdatetimeモジュールをimportして型に使用できる。

説明 JSONでの対応
datetime.date 日付 ISO8601形式の日付を表す文字列、またはUnixタイムスタンプを表す数値
datetime.time 時刻 ISO8601形式の時刻を表す文字列
datetime.datetime タイムスタンプ(日時) ISO8601形式の日時を表す文字列、またはUnixタイムスタンプを表す数値
datetime.timedelta 時間間隔 ISO8601形式の時間間隔を表す文字列、または時間間隔(秒)を表す数値
  • 型ヒントを使うため、List[str]のように要素の型も指定できる
    • JSON schemaではitemstypeの指定になる
  • また、UnionOptionalも使用できる
    • Unionの場合、JSON schemaではoneOf指定になる
    • Optionalの場合、JSON schemaではrequiredが指定されない

必須チェックとデフォルト値

プロパティの必須チェックには次の4パターンの類型がある。

厳密な必須チェック(プロパティが存在し値がNoneではない)

from pydantic import BaseModel

class Hoge(BaseModel):
    hoge: int

と書けば次の場合にエラーになる

  • hogeが存在しない
  • hogeが存在してもその値がNone
  • hogeが整数ではない

緩い必須チェック(プロパティが存在すれば値はNoneでもいい)

from pydantic import BaseModel, Field

class Hoge(BaseModel):
    hoge: Optional[int] = Field(...)

と書けば次の場合にエラーになる

  • hogeが存在しない
  • hogeが整数ではない

必須チェックなし(プロパティが存在しなくてもいい)

from pydantic import BaseModel, Field

class Hoge(BaseModel):
    hoge: Optional[int]

と書けば次の場合にエラーになる

  • hogeが整数ではない

値が必要なオプション(プロパティは存在しなくてもいいが、存在するならnullではダメなもの)

冗長な書き方が必要となる。

from pydantic import BaseModel, Field, validator

class Hoge(BaseModel):
    hoge: Optional[int]
    
    @validator("hoge")                # hogeのバリデーションの登録 
    def validate_hoge(cls, value):    # 関数名はなんでもいい。第1引数はcls固定で使用しない。第2引数はvalueでhogeに設定した値
        if value is None:             # Noneであれば例外を投げる
            raise TypeError("none is not an allowed value") 
        return value                  # Noneでない場合はそのままvalueを返す

と書けば次の場合にエラーになる

  • hogeNone
  • hogeが整数ではない

デフォルト値

from pydantic import BaseModel, Field

class Hoge(BaseModel):
    hoge: int = 1
    # または
    fuga: int = Field(2)

と書けばデフォルト値を設定できる。デフォルト値がある場合は値を明示的に設定しなくてもデフォルト値が設定されていることになるので必須チェックにはかからない。

Field() の第1引数にはデフォルト値を設定するが、デフォルト値が不要な場合は、...(Ellipsis)を書く。

形式チェック

文字列の長さ

from pydantic import BaseModel, Field

class Hoge(BaseModel):
    foo: str = Field(..., min_length=2, max_length=10)

と書けば、文字数が2文字から10文字以外の場合エラーとなる。
また、デフォルト値の設定が...となっているので、値を設定しない場合は必須エラーとなる。

リストの要素数

from pydantic import BaseModel, Field

class Hoge(BaseModel):
    foo: List[str] = Field(..., min_items=2, max_items=10)

と書けば、要素数が2個から10個以外の場合エラーとなる。

正規表現

from pydantic import BaseModel, Field

class Hoge(BaseModel):
    foo: str = Field(..., regex=r"[0-9]{2,3}")

と書けば、regex=r"[0-9]{2,3}"で指定した正規表現にマッチしない場合エラーとなる。

値チェック

数値の範囲

from pydantic import BaseModel, Field

class Hoge(BaseModel):
    hoge: int = Field(..., ge=2, le=10)

と書けば、値が2から10の範囲外の場合エラーとなる。

次の4種類が使用できる

  • ge: >= greater than or equal 以上
  • gt: > greater than 超過
  • le: <=less than or equal 以下
  • lt: < less than 未満

コード定義

from enum import Enum
from pydantic import BaseModel, Field

class HogeCode(Enum):
    AAA = 1
    BBB = 2
    CCC = 3

class Hoge(BaseModel):
    foo: HogeCode

と書けば、値が1, 2, 3以外の場合エラーとなる。

複雑なバリデーション

validatorを実装する。詳しくはValidatorsを参照。

from pydantic import BaseModel, Field, validator

class Hoge(BaseModel):
    hoge: Optional[int]
    
    @validator(プロパティ名) 
    def validate_hoge(cls, value, values):
        if ここでチェック:
            raise ValueError("何かのメッセージ") 
        return value
  • プロパティ名を@validatorデコレーターに指定するとそのプロパティがチェックされる
  • @validatorデコレーターにeach_item=Trueを設定するとチェックしたいプロパティがListDictなどのコレクションの場合、要素ひとつひとつについてバリデートできる。
  • validatorの関数名は任意
  • validatorの関数の引数は第1、第2引数は固定、第3引数以降は任意だがvaluesという名前の引数を定義すると、そのプロパティより前にバリデートされたプロパティの辞書(プロパティ名がキーで、プロパティの値が値)が入ってくる。
    • これを使って相関チェックが実装できる
  • raiseできる例外クラスはTypeErrorValueError 自体かサブクラスで詳細は後述する。
    • raiseせずにassertを使ってAssertionErrorを投げても問題ない

Pydanticでは実装できないバリデーション

データベースの内容との相関チェックはPydantic単体ではバリデーションできないため、パスオペレーション関数(@app.get(...とかをつけた関数)の先頭でチェックする。

例えば、リクエストボディが次のような形式だとしてquxにエラーがあった場合は次のように返却する。

request
{
    "foo": {
        "bar": 123,
        "baz": {
            "qux": "errrrrrr"  // これがエラーとする
        }
    }
}
from fastapi.exceptions import RequestValidationError
from pydantic.error_wrappers import ErrorWrapper

# ...中略...

if エラー判定:
    raise RequestValidationError(
        [ErrorWrapper(ValueError("エラーメッセージをどうぞ"), ("body", "foo", "baz", "qux"))]
    )

RequestValidationErrorを利用することで他のバリデーションエラーと同じ形式で出力される。
エラーレスポンスは次のようになる

422 Unprocessable Entity

response
{
  "detail": [
    {
      "loc": [
        "body",
        "foo",
        "bar",
        "qux"
      ],
      "msg": "エラーメッセージをどうぞ",
      "type": "value_error"
    }
  ]
}

例外クラスとtypeについての詳細

  • ErrorWrapper に指定できる例外クラスは@validatorraiseできるものと同じである
  • ValueErrorの場合、レスポンスに出力されるtypevalue_errorとなる
  • TypeErrorの場合、レスポンスに出力されるtypetype_errorとなる
  • ValueErrorのサブクラスを実装しクラス変数としてcode="piyopiyo"を定義すると、レスポンスに出力されるtypevalue_error.piyopiyoとなる。(TypeErrorのサブクラスの場合も同様)
60
44
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?