コンテンツへスキップ

クエリパラメータと文字列の検証

FastAPI では、パラメータに追加情報と検証を宣言できます。

このアプリケーションを例に見てみましょう。

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

クエリパラメータ qstr | None 型です。これは str 型ですが、None になる可能性もあります。実際、デフォルト値は None なので、FastAPI はそれが必須ではないことを認識します。

FastAPI は、デフォルト値が = None であるため、q の値が必須ではないことを認識します。

str | None を使用することで、エディタがより良いサポートを提供し、エラーを検出できるようになります。

追加の検証

q はオプションですが、提供される場合は**その長さが50文字を超えない**ように強制します。

QueryAnnotated をインポートする

これを実現するために、まず以下をインポートします。

  • fastapi から Query
  • typing から Annotated
from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

情報

FastAPI はバージョン 0.95.0 で Annotated のサポートを追加し(そして推奨し始めました)。

古いバージョンを使用している場合、Annotated を使用しようとするとエラーが発生します。

Annotated を使用する前に、FastAPI のバージョンを 0.95.1 以上にアップグレードしてください。

q パラメータの型に Annotated を使用する

Python 型入門で、Annotated をパラメータにメタデータを追加するために使用できると前にお伝えしました。

FastAPI でそれを使用する時が来ました。🚀

この型アノテーションがありました。

q: str | None = None
q: Union[str, None] = None

これを Annotated でラップすると、次のようになります。

q: Annotated[str | None] = None
q: Annotated[Union[str, None]] = None

これらの両方のバージョンは同じ意味を持ちます。qstr または None になり得るパラメータで、デフォルトでは None です。

さあ、楽しい部分に進みましょう。🎉

q パラメータの AnnotatedQuery を追加する

より多くの情報(この場合は追加の検証)を入れられるこの Annotated ができたので、Annotated の中に Query を追加し、パラメータ max_length50 に設定します。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

デフォルト値は依然として None であるため、パラメータは引き続きオプションです。

しかし、Annotated の中に Query(max_length=50) を持っていることで、FastAPI にこの値に対して**追加の検証**を行いたい、最大50文字にしたい、と伝えているのです。😎

ヒント

ここでは**クエリパラメータ**なので Query() を使用しています。後で Path(), Body(), Header(), Cookie() のような他のものも同じ引数を受け入れることがわかります。

FastAPI は次のことを行います。

  • 最大長が50文字であることを確認するためにデータを**検証**します。
  • データが無効な場合にクライアントに**明確なエラー**を表示します。
  • OpenAPI スキーマの*パス操作*にパラメータを**文書化**します(これにより、**自動ドキュメント UI** に表示されます)。

代替(旧):デフォルト値としての Query

FastAPI の以前のバージョン(0.95.0 以前)では、Annotated に入れる代わりに、パラメータのデフォルト値として Query を使用する必要がありました。それを使用しているコードをよく見かける可能性があるので、説明しておきます。

ヒント

新しいコードや可能な限り、上記で説明したように Annotated を使用してください。いくつかの利点(以下で説明)があり、欠点はありません。🍰

これは、関数のパラメータのデフォルト値として Query() を使用し、パラメータ max_length を 50 に設定する方法です。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

この場合(Annotated を使用しない場合)、関数のデフォルト値 NoneQuery() に置き換える必要があるため、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 スキーマのパス操作にパラメータが文書化されます。

デフォルト値として、または Annotated 内の Query

Annotated の中で Query を使用する場合、Querydefault パラメータは使用できないことに注意してください。

代わりに、関数のパラメータの実際のデフォルト値を使用してください。そうしないと、一貫性がなくなります。

たとえば、これは許可されません。

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 パラメータも追加できます。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(min_length=3, max_length=50)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, min_length=3, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(default=None, min_length=3, max_length=50),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

正規表現を追加する

パラメータが一致する必要がある正規表現 pattern を定義できます。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$")
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$")
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$")
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None, min_length=3, max_length=50, pattern="^fixedquery$"
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(
        default=None, min_length=3, max_length=50, pattern="^fixedquery$"
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

この特定の正規表現パターンは、受信したパラメータ値が以下であることを確認します。

  • ^: 以下の文字で始まる。前に文字がない。
  • fixedquery: 正確な値 fixedquery を持つ。
  • $: そこで終わる。fixedquery の後にこれ以上文字がない。

これらの**「正規表現」**の概念に戸惑うなら、心配しないでください。多くの人にとって難しいトピックです。まだ正規表現が必要なくても、多くのことを行うことができます。

これで、必要なときにいつでも**FastAPI**で正規表現を使用できることがわかりました。

Pydantic v1 regex の代わりに pattern

Pydantic バージョン 2 および FastAPI 0.100.0 より前は、パラメータは pattern の代わりに regex と呼ばれていましたが、現在は非推奨です。

まだそれを使用しているコードを見かけるかもしれません。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None, Query(min_length=3, max_length=50, regex="^fixedquery$")
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

しかし、これは非推奨であり、新しいパラメータ pattern を使用するように更新する必要があることを知っておいてください。🤓

デフォルト値

もちろん、None 以外のデフォルト値を使用することもできます。

q クエリパラメータに min_length3 と宣言し、デフォルト値を "fixedquery" にしたいとします。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(default="fixedquery", min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

None を含む任意の型のデフォルト値を持つことで、パラメータはオプション(必須ではない)になります。

必須パラメータ

追加の検証やメタデータを宣言する必要がない場合は、q クエリパラメータを、次のようにデフォルト値を宣言しないだけで必須にできます。

q: str

代わりに

q: str | None = None

しかし、今回は Query を使って宣言しています。例えば、

q: Annotated[str | None, Query(min_length=3)] = None

したがって、Query を使用しながら値を必須として宣言する必要がある場合は、デフォルト値を宣言しないだけで済みます。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

必須、None でも可

パラメータが None を受け入れることを宣言できますが、それでも必須です。これにより、クライアントは、値が None であっても値を送信することを強制されます。

そのためには、None が有効な型であることを宣言し、デフォルト値を宣言しないだけです。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(min_length=3)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

クエリパラメータのリスト / 複数値

Query でクエリパラメータを明示的に定義すると、値のリストを受け取る、つまり複数の値を受け取るように宣言することもできます。

たとえば、URL に複数回出現する可能性のあるクエリパラメータ q を宣言するには、次のように記述します。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list[str] | None, Query()] = None):
    query_items = {"q": q}
    return query_items
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[list[str], None], Query()] = None):
    query_items = {"q": q}
    return query_items
from typing import List, Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[List[str], None], Query()] = None):
    query_items = {"q": q}
    return query_items

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list[str] | None = Query(default=None)):
    query_items = {"q": q}
    return query_items

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[list[str], None] = Query(default=None)):
    query_items = {"q": q}
    return query_items

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import List, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[List[str], None] = Query(default=None)):
    query_items = {"q": q}
    return query_items

その後、次のような URL で

https://:8000/items/?q=foo&q=bar

複数の q *クエリパラメータ*の値(foobar)を、*パス操作関数*内の Python list で、*関数パラメータ* q に受け取ります。

したがって、そのURLへの応答は次のようになります。

{
  "q": [
    "foo",
    "bar"
  ]
}

ヒント

上記の例のように、list 型のクエリパラメータを宣言するには、Query を明示的に使用する必要があります。そうしないと、リクエストボディとして解釈されます。

インタラクティブな API ドキュメントは、複数の値を許可するように適切に更新されます。

クエリパラメータのリスト / デフォルト値を持つ複数値

値が指定されていない場合に、デフォルトの list を定義することもできます。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]):
    query_items = {"q": q}
    return query_items
🤓 その他のバージョンとバリアント
from typing import List

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[List[str], Query()] = ["foo", "bar"]):
    query_items = {"q": q}
    return query_items

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list[str] = Query(default=["foo", "bar"])):
    query_items = {"q": q}
    return query_items

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import List

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: List[str] = Query(default=["foo", "bar"])):
    query_items = {"q": q}
    return query_items

もし

https://:8000/items/

へアクセスすると、q のデフォルト値は ["foo", "bar"] になり、応答は次のようになります。

{
  "q": [
    "foo",
    "bar"
  ]
}

list のみを使用する

list[str] の代わりに list を直接使用することもできます。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list, Query()] = []):
    query_items = {"q": q}
    return query_items
🤓 その他のバージョンとバリアント
from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[list, Query()] = []):
    query_items = {"q": q}
    return query_items

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: list = Query(default=[])):
    query_items = {"q": q}
    return query_items

この場合、FastAPI はリストの内容をチェックしないことに注意してください。

たとえば、list[int] はリストの内容が整数であることをチェック(およびドキュメント化)しますが、list 単独ではチェックしません。

その他のメタデータを宣言する

パラメータに関する追加情報を追加できます。

この情報は生成された OpenAPI に含まれ、ドキュメントのユーザーインターフェースや外部ツールで使用されます。

異なるツールは、OpenAPI のサポートレベルが異なる場合があることに注意してください。

一部のツールでは、宣言されたすべての追加情報がまだ表示されない場合がありますが、ほとんどの場合、不足している機能はすでに開発が予定されています。

title を追加できます。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(title="Query string", min_length=3)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(default=None, title="Query string", min_length=3),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(default=None, title="Query string", min_length=3),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

そして description

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None,
        Query(
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None],
        Query(
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None],
        Query(
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(
        default=None,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

エイリアスパラメータ

パラメータを item-query にしたいと想像してください。

次のように

http://127.0.0.1:8000/items/?item-query=foobaritems

しかし、item-query は有効な Python 変数名ではありません。

最も近いのは item_query でしょう。

しかし、それでも厳密に item-query である必要があります...

その場合、alias を宣言できます。そのエイリアスが、パラメータの値を見つけるために使用されます。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(alias="item-query")] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str | None = Query(default=None, alias="item-query")):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, alias="item-query")):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

非推奨のパラメータ

さて、このパラメータがもう気に入らないとしましょう。

クライアントが使用しているため、しばらくは残しておく必要がありますが、ドキュメントではそれを非推奨として明確に表示したいと考えています。

次に、Query にパラメータ deprecated=True を渡します。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        str | None,
        Query(
            alias="item-query",
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
            max_length=50,
            pattern="^fixedquery$",
            deprecated=True,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None],
        Query(
            alias="item-query",
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
            max_length=50,
            pattern="^fixedquery$",
            deprecated=True,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Annotated[
        Union[str, None],
        Query(
            alias="item-query",
            title="Query string",
            description="Query string for the items to search in the database that have a good match",
            min_length=3,
            max_length=50,
            pattern="^fixedquery$",
            deprecated=True,
        ),
    ] = None,
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None,
        alias="item-query",
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
        max_length=50,
        pattern="^fixedquery$",
        deprecated=True,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    q: Union[str, None] = Query(
        default=None,
        alias="item-query",
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
        max_length=50,
        pattern="^fixedquery$",
        deprecated=True,
    ),
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

ドキュメントには次のように表示されます。

OpenAPI からパラメータを除外する

生成された OpenAPI スキーマ (および自動ドキュメントシステム) からクエリパラメータを除外するには、Query のパラメータ include_in_schemaFalse に設定します。

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Annotated[str | None, Query(include_in_schema=False)] = None,
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}
🤓 その他のバージョンとバリアント
from typing import Annotated, Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None,
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}
from typing import Union

from fastapi import FastAPI, Query
from typing_extensions import Annotated

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None,
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: str | None = Query(default=None, include_in_schema=False),
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(
    hidden_query: Union[str, None] = Query(default=None, include_in_schema=False),
):
    if hidden_query:
        return {"hidden_query": hidden_query}
    else:
        return {"hidden_query": "Not found"}

カスタム検証

上記で示したパラメータではできない、いくつかの**カスタム検証**を行う必要がある場合があります。

そのような場合、通常の検証(例:値が str であることの検証後)の後で適用される**カスタムバリデータ関数**を使用できます。

これは、Annotated 内で Pydantic の AfterValidator を使用して実現できます。

ヒント

Pydantic には BeforeValidator などもあります。🤓

たとえば、このカスタムバリデーターは、項目 ID が ISBN の場合は isbn- で始まるか、IMDB の映画 URL ID の場合は imdb- で始まることをチェックします。

import random
from typing import Annotated

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}
🤓 その他のバージョンとバリアント
import random
from typing import Annotated, Union

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}
import random
from typing import Union

from fastapi import FastAPI
from pydantic import AfterValidator
from typing_extensions import Annotated

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}

情報

これは Pydantic バージョン 2 以降で利用可能です。😎

ヒント

データベースや別のAPIなど、**外部コンポーネント**との通信を必要とするあらゆる種類の検証が必要な場合は、代わりに**FastAPIの依存関係**を使用する必要があります。これらについては後で学習します。

これらのカスタムバリデーターは、リクエストで提供された**同じデータのみ**でチェックできるもの向けです。

コードを理解する

重要な点は、**Annotated 内で関数とともに AfterValidator を使用する**ことだけです。この部分は自由にスキップしてください。🤸


しかし、この特定のコード例に興味があり、まだ楽しんでいるのであれば、いくつかの追加の詳細を説明します。

value.startswith() を使用する文字列

気づきましたか? value.startswith() を使用する文字列はタプルを受け取ることができ、タプルの各値をチェックします。

# Code above omitted 👆

def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id

# Code below omitted 👇
👀 ファイル全体のプレビュー
import random
from typing import Annotated

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}
🤓 その他のバージョンとバリアント
import random
from typing import Annotated, Union

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}
import random
from typing import Union

from fastapi import FastAPI
from pydantic import AfterValidator
from typing_extensions import Annotated

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}

ランダムなアイテム

data.items() を使用すると、各辞書項目のキーと値を含むタプルを持つイテラブルオブジェクトが取得されます。

このイテラブルオブジェクトを list(data.items()) で適切な list に変換します。

次に、random.choice() を使用してリストから**ランダムな値**を取得し、(id, name) のタプルを取得します。これは ("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy") のようなものになります。

そして、そのタプルの**2つの値を**変数 idname に**割り当てます**。

したがって、ユーザーが項目 ID を指定しなかった場合でも、ランダムな提案が受け取られます。

...これらすべてを**1つのシンプルな行**で行います。🤯 Pythonが好きじゃないですか?🐍

# Code above omitted 👆

@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}
👀 ファイル全体のプレビュー
import random
from typing import Annotated

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}
🤓 その他のバージョンとバリアント
import random
from typing import Annotated, Union

from fastapi import FastAPI
from pydantic import AfterValidator

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}
import random
from typing import Union

from fastapi import FastAPI
from pydantic import AfterValidator
from typing_extensions import Annotated

app = FastAPI()

data = {
    "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
    "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
    "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}


def check_valid_id(id: str):
    if not id.startswith(("isbn-", "imdb-")):
        raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
    return id


@app.get("/items/")
async def read_items(
    id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None,
):
    if id:
        item = data.get(id)
    else:
        id, item = random.choice(list(data.items()))
    return {"id": id, "name": item}

まとめ

パラメータに追加の検証とメタデータを宣言できます。

一般的な検証とメタデータ

  • エイリアス
  • タイトル
  • 説明
  • 非推奨

文字列に特化した検証

  • min_length
  • max_length
  • pattern

AfterValidator を使用したカスタム検証。

これらの例では、str 値の検証を宣言する方法を示しました。

数値のような他の型の検証を宣言する方法については、次の章を参照してください。