21 KiB
クエリパラメータと文字列の検証
FastAPI ではパラメータの追加情報とバリデーションを宣言することができます。
以下のアプリケーションを例にしてみましょう:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
クエリパラメータ q は str | None 型で、str 型ですが None にもなり得ることを意味し、実際にデフォルト値は None なので、FastAPIはそれが必須ではないと理解します。
/// note | 備考
FastAPIは、 q はデフォルト値が = None であるため、必須ではないと理解します。
str | None を使うことで、エディターによるより良いサポートとエラー検出を可能にします。
///
バリデーションの追加
qはオプショナルですが、もし値が渡されてきた場合には、長さが50文字を超えないことを強制してみましょう。
Query と Annotated のインポート
そのために、まずは以下をインポートします:
fastapiからQuerytypingからAnnotated
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
/// info | 情報
FastAPI はバージョン 0.95.0 で Annotated のサポートを追加し(推奨し始め)ました。
古いバージョンの場合、Annotated を使おうとするとエラーになります。
Annotated を使う前に、FastAPI のバージョンを少なくとも 0.95.1 にするために、FastAPI のバージョンをアップグレード{.internal-link target=_blank}してください。
///
q パラメータの型で Annotated を使う
以前、Python Types Intro{.internal-link target=_blank} で Annotated を使ってパラメータにメタデータを追加できると説明したことを覚えていますか?
いよいよ FastAPI で使うときです。 🚀
次の型アノテーションがありました:
//// tab | Python 3.10+
q: str | None = None
////
//// tab | Python 3.9+
q: Union[str, None] = None
////
これを Annotated で包んで、次のようにします:
//// tab | Python 3.10+
q: Annotated[str | None] = None
////
//// tab | Python 3.9+
q: Annotated[Union[str, None]] = None
////
どちらも同じ意味で、q は str または None になり得るパラメータで、デフォルトでは None です。
では、面白いところに進みましょう。 🎉
q パラメータの Annotated に Query を追加する
追加情報(この場合は追加のバリデーション)を入れられる Annotated ができたので、Annotated の中に Query を追加し、パラメータ max_length を 50 に設定します:
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
デフォルト値は引き続き None なので、このパラメータは依然としてオプショナルです。
しかし、Annotated の中に Query(max_length=50) を入れることで、この値に 追加のバリデーション をしたい、最大 50 文字にしたい、と FastAPI に伝えています。 😎
/// tip | 豆知識
ここでは クエリパラメータ なので Query() を使っています。後で Path()、Body()、Header()、Cookie() など、Query() と同じ引数を受け取れるものも見ていきます。
///
FastAPI は次を行います:
- 最大長が 50 文字であることを確かめるようデータを 検証 する
- データが有効でないときに、クライアントに 明確なエラー を表示する
- OpenAPI スキーマの path operation にパラメータを ドキュメント化 する(その結果、自動ドキュメント UI に表示されます)
代替(古い方法): デフォルト値としての Query
FastAPI の以前のバージョン(0.95.0 より前)では、パラメータのデフォルト値として Query を使う必要があり、Annotated の中に入れるのではありませんでした。これを使ったコードを見かける可能性が高いので、説明します。
/// tip | 豆知識
新しいコードでは、可能な限り上で説明したとおり Annotated を使ってください。複数の利点(後述)があり、欠点はありません。 🍰
///
関数パラメータのデフォルト値として Query() を使い、パラメータ max_length を 50 に設定する方法は次のとおりです:
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
この場合(Annotated を使わない場合)、関数内のデフォルト値 None を Query() に置き換える必要があるため、Query(default=None) のパラメータでデフォルト値を設定する必要があります。これは(少なくとも FastAPI にとっては)そのデフォルト値を定義するのと同じ目的を果たします。
なので:
q: str | None = Query(default=None)
...はデフォルト値 None を持つオプショナルなパラメータになり、以下と同じです:
q: str | None = None
ただし Query のバージョンでは、クエリパラメータであることを明示的に宣言しています。
そして、さらに多くのパラメータをQueryに渡すことができます。この場合、文字列に適用される、max_lengthパラメータを指定します。
q: str | None = Query(default=None, max_length=50)
これにより、データを検証し、データが有効でない場合は明確なエラーを表示し、OpenAPIスキーマの path operation にパラメータを記載します。
デフォルト値としての Query または Annotated 内の Query
Annotated の中で Query を使う場合、Query の default パラメータは使えないことに注意してください。
その代わりに、関数パラメータの実際のデフォルト値を使います。そうしないと整合性が取れなくなります。
例えば、これは許可されません:
q: Annotated[str, Query(default="rick")] = "morty"
...なぜなら、デフォルト値が "rick" なのか "morty" なのかが不明確だからです。
そのため、(できれば)次のようにします:
q: Annotated[str, Query()] = "rick"
...または、古いコードベースでは次のようなものが見つかるでしょう:
q: str = Query(default="rick")
Annotated の利点
関数パラメータのデフォルト値スタイルではなく、Annotated を使うことが推奨 されます。複数の理由で より良い からです。 🤓
関数パラメータ の デフォルト値 は 実際のデフォルト値 であり、Python 全般としてより直感的です。 😌
FastAPI なしで同じ関数を 別の場所 から 呼び出しても、期待どおりに動作 します。必須 パラメータ(デフォルト値がない)があれば、エディター がエラーで知らせてくれますし、Python も必須パラメータを渡さずに実行すると文句を言います。
Annotated を使わずに (古い)デフォルト値スタイル を使う場合、FastAPI なしでその関数を 別の場所 で呼び出すとき、正しく動かすために関数へ引数を渡すことを 覚えておく 必要があります。そうしないと値が期待と異なります(例えば str の代わりに QueryInfo か、それに類するものになります)。また、エディターも警告せず、Python もその関数の実行で文句を言いません。内部の処理がエラーになるときに初めて問題が出ます。
Annotated は複数のメタデータアノテーションを持てるので、Typer のような別ツールと同じ関数を使うこともできます。 🚀
バリデーションをさらに追加する
パラメータmin_lengthも追加することができます:
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
正規表現の追加
パラメータが一致するべき 正規表現 pattern を定義することができます:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
この特定の正規表現パターンは受け取ったパラメータの値をチェックします:
^: は、これ以降の文字で始まり、これより以前には文字はありません。fixedquery: は、正確なfixedqueryを持っています.$: で終わる場合、fixedquery以降には文字はありません.
もしこれらすべての 「正規表現」 のアイデアについて迷っていても、心配しないでください。多くの人にとって難しい話題です。正規表現を必要としなくても、まだ、多くのことができます。
これで、必要になったときにはいつでも FastAPI で使えることが分かりました。
デフォルト値
もちろん、None 以外のデフォルト値も使えます。
クエリパラメータ q の min_length を 3 とし、デフォルト値を "fixedquery" として宣言したいとします:
{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *}
/// note | 備考
None を含む任意の型のデフォルト値があると、パラメータはオプショナル(必須ではない)になります。
///
必須パラメータ
これ以上、バリデーションやメタデータを宣言する必要がない場合は、デフォルト値を宣言しないだけでクエリパラメータ q を必須にできます。以下のように:
q: str
以下の代わりに:
q: str | None = None
しかし今は、例えば次のように Query で宣言しています:
q: Annotated[str | None, Query(min_length=3)] = None
そのため、Query を使いながら値を必須として宣言したい場合は、単にデフォルト値を宣言しません:
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
必須、None にできる
パラメータが None を受け付けるが、それでも必須である、と宣言できます。これにより、値が None であってもクライアントは値を送らなければならなくなります。
そのために、None が有効な型であることを宣言しつつ、単にデフォルト値を宣言しません:
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
クエリパラメータのリスト / 複数の値
クエリパラメータを明示的に Query で定義すると、値のリストを受け取るように宣言したり、言い換えると複数の値を受け取るように宣言したりすることもできます。
例えば、URL内に複数回出現するクエリパラメータqを宣言するには以下のように書きます:
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *}
そして、次のような URL なら:
http://localhost:8000/items/?q=foo&q=bar
path operation function 内の function parameter q で、複数の q query parameters' 値(foo と bar)を Python の list として受け取ります。
そのため、このURLのレスポンスは以下のようになります:
{
"q": [
"foo",
"bar"
]
}
/// tip | 豆知識
上述の例のように、list型のクエリパラメータを宣言するには明示的にQueryを使用する必要があります。そうしない場合、リクエストボディと解釈されます。
///
対話的APIドキュメントは複数の値を許可するために自動的に更新されます。
デフォルト値を持つ、クエリパラメータのリスト / 複数の値
また、値が指定されていない場合はデフォルトの list を定義することもできます。
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
以下にアクセスすると:
http://localhost:8000/items/
qのデフォルトは: ["foo", "bar"] となり、レスポンスは以下のようになります:
{
"q": [
"foo",
"bar"
]
}
list だけを使う
list[str] の代わりに直接 list を使うこともできます:
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
/// note | 備考
この場合、FastAPIはリストの内容をチェックしないことを覚えておいてください。
例えばlist[int]はリストの内容が整数であるかどうかをチェックします(そして、文書化します)。しかしlistだけではそうしません。
///
より多くのメタデータを宣言する
パラメータに関する情報をさらに追加することができます。
その情報は、生成されたOpenAPIに含まれ、ドキュメントのユーザーインターフェースや外部のツールで使用されます。
/// note | 備考
ツールによってOpenAPIのサポートのレベルが異なる可能性があることを覚えておいてください。
その中には、宣言されたすべての追加情報が表示されていないものもあるかもしれませんが、ほとんどの場合、不足している機能はすでに開発の計画がされています。
///
titleを追加できます:
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
descriptionを追加できます:
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
エイリアスパラメータ
パラメータにitem-queryを指定するとします.
以下のような感じです:
http://127.0.0.1:8000/items/?item-query=foobaritems
しかし、item-queryは有効なPythonの変数名ではありません。
最も近いのはitem_queryでしょう。
しかし、どうしてもitem-queryと正確に一致している必要があるとします...
それならば、aliasを宣言することができます。エイリアスはパラメータの値を見つけるのに使用されます:
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
パラメータを非推奨にする
さて、このパラメータが気に入らなくなったとしましょう。
それを使っているクライアントがいるので、しばらくは残しておく必要がありますが、ドキュメントにはdeprecatedと明記しておきたいです。
その場合、Queryにパラメータdeprecated=Trueを渡します:
{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
ドキュメントは以下のようになります:
OpenAPI からパラメータを除外する
生成される OpenAPI スキーマ(つまり自動ドキュメントシステム)からクエリパラメータを除外するには、Query のパラメータ include_in_schema を False に設定します:
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
カスタムバリデーション
上で示したパラメータではできない カスタムバリデーション が必要になる場合があります。
その場合、通常のバリデーション(例: 値が str であることの検証)の後に適用される カスタムバリデータ関数 を使えます。
これを行うには、Annotated の中で Pydantic の AfterValidator を使います。
/// tip | 豆知識
Pydantic には BeforeValidator などもあります。 🤓
///
例えば、このカスタムバリデータは、ISBN の書籍番号なら item ID が isbn- で始まること、IMDB の movie URL ID なら imdb- で始まることをチェックします:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
/// info | 情報
これは Pydantic バージョン 2 以上で利用できます。 😎
///
/// tip | 豆知識
データベースや別の API など、何らかの 外部コンポーネント との通信が必要なタイプのバリデーションを行う必要がある場合は、代わりに FastAPI Dependencies を使うべきです。これについては後で学びます。
これらのカスタムバリデータは、リクエストで提供された 同じデータのみ でチェックできるもの向けです。
///
そのコードを理解する
重要なのは、Annotated の中で関数と一緒に AfterValidator を使うこと だけです。この部分は飛ばしても構いません。 🤸
ただし、この具体的なコード例が気になっていて、まだ興味が続くなら、追加の詳細を示します。
value.startswith() を使う文字列
気づきましたか?value.startswith() を使う文字列はタプルを受け取れ、そのタプル内の各値をチェックします:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
ランダムなアイテム
data.items() で、辞書の各アイテムのキーと値を含むタプルを持つ 反復可能オブジェクト を取得します。
この反復可能オブジェクトを list(data.items()) で適切な list に変換します。
そして random.choice() でその list から ランダムな値 を取得するので、(id, name) のタプルを得ます。これは ("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy") のようになります。
次に、そのタプルの 2つの値を代入 して、変数 id と name に入れます。
つまり、ユーザーが item ID を提供しなかった場合でも、ランダムな提案を受け取ります。
...これを 単一のシンプルな1行 で行っています。 🤯 Python が好きになりませんか? 🐍
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
まとめ
パラメータに追加のバリデーションとメタデータを宣言することができます。
一般的なバリデーションとメタデータ:
aliastitledescriptiondeprecated
文字列に固有のバリデーション:
min_lengthmax_lengthpattern
AfterValidator を使ったカスタムバリデーション。
この例では、str の値のバリデーションを宣言する方法を見てきました。
数値のような他の型のバリデーションを宣言する方法は次の章を参照してください。