コンテンツにスキップ

フォームモデル

AI coding agents

Use the official FastAPI skill for your installed version. Install it from your project directory:

uvx library-skills

See Develop with AI.

🌐 AI と人間による翻訳

この翻訳は、人間のガイドに基づいて AI によって作成されました。🤝

原文の意図を取り違えていたり、不自然な表現になっている可能性があります。🤖

AI LLM をより適切に誘導するのを手伝う ことで、この翻訳を改善できます。

英語版

FastAPI では、フォームフィールドを宣言するために Pydantic モデルを使用できます。

備考

フォームを使うには、まず python-multipart をインストールします。

プロジェクトに追加します:

$ uv add python-multipart

備考

これは FastAPI バージョン 0.113.0 以降でサポートされています。🤓

フォーム用の Pydantic モデル

受け取りたいフィールドを フォームフィールド として持つ Pydantic モデル を宣言し、パラメータを Form として宣言するだけです:

from typing import Annotated

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class FormData(BaseModel):
    username: str
    password: str


@app.post("/login/")
async def login(data: Annotated[FormData, Form()]):
    return data
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class FormData(BaseModel):
    username: str
    password: str


@app.post("/login/")
async def login(data: FormData = Form()):
    return data

FastAPI はリクエストの フォームデータ から 各フィールド のデータを 抽出 し、定義した Pydantic モデルとして渡します。

HTML チェックボックスには、デフォルト値を False とした真偽値フィールドを宣言します。

ドキュメントで確認

/docs のドキュメント UI で確認できます:

追加のフォームフィールドを禁止

一部の特殊なユースケース(おそらくあまり一般的ではありません)では、フォームフィールドを Pydantic モデルで宣言したもののみに制限し、追加のフィールドを禁止したい場合があります。

備考

これは FastAPI バージョン 0.114.0 以降でサポートされています。🤓

Pydantic のモデル設定で、extra フィールドを forbid にできます:

from typing import Annotated

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class FormData(BaseModel):
    username: str
    password: str
    model_config = {"extra": "forbid"}


@app.post("/login/")
async def login(data: Annotated[FormData, Form()]):
    return data
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class FormData(BaseModel):
    username: str
    password: str
    model_config = {"extra": "forbid"}


@app.post("/login/")
async def login(data: FormData = Form()):
    return data

クライアントが余分なデータを送信しようとすると、エラーのレスポンスを受け取ります。

例えば、クライアントが次のフォームフィールドを送ろうとした場合:

  • username: Rick
  • password: Portal Gun
  • extra: Mr. Poopybutthole

フィールド extra は許可されていない旨のエラーレスポンスが返されます:

{
    "detail": [
        {
            "type": "extra_forbidden",
            "loc": ["body", "extra"],
            "msg": "Extra inputs are not permitted",
            "input": "Mr. Poopybutthole"
        }
    ]
}

フォームモデルの組み合わせ

複数のフォームモデルと個別の Form パラメータを組み合わせることができます。

from typing import Annotated

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Profile(BaseModel):
    name: str


class Preferences(BaseModel):
    tags: list[str] = []


@app.post("/profiles/")
async def create_profile(
    profile: Annotated[Profile, Form()],
    preferences: Annotated[Preferences, Form()],
    token: Annotated[str, Form()],
):
    return {"profile": profile, "preferences": preferences, "token": token}
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Profile(BaseModel):
    name: str


class Preferences(BaseModel):
    tags: list[str] = []


@app.post("/profiles/")
async def create_profile(
    profile: Profile = Form(),
    preferences: Preferences = Form(),
    token: str = Form(),
):
    return {"profile": profile, "preferences": preferences, "token": token}

クライアントは name、tags、token をフォームフィールドとして送信します。各モデルはフォームフィールドを個別に検証します。

各モデルの extra 設定は、他のパラメータや依存関係で宣言されたフィールドやファイルを含む、すべてのフォームフィールドに適用されます。extra="forbid" では、そのモデルにないフィールドはすべて拒否されます。extra="allow" では、それらのフィールドはモデルの追加データとして含まれます。

複数の宣言で同じフォームフィールド名を使用すると、それぞれがそのフィールドの値を検証します。OpenAPI スキーマで一貫した記述ができるように、共有フィールドには互換性のある型と制約を使用してください。

オプションのフォームモデル

フォームモデルに | None とデフォルト値 None を指定して宣言すると、フィールドのグループをオプションにできます。

from typing import Annotated

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Profile(BaseModel):
    name: str
    age: int


@app.post("/profiles/")
async def create_profile(
    token: Annotated[str, Form()],
    profile: Annotated[Profile | None, Form()] = None,
):
    return {"token": token, "profile": profile}
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Profile(BaseModel):
    name: str
    age: int


@app.post("/profiles/")
async def create_profile(
    token: str = Form(),
    profile: Profile | None = Form(default=None),
):
    return {"token": token, "profile": profile}

name も age も送信されない場合、profile は None になります。どちらかが送信された場合、FastAPI はモデルを検証し、両方のフィールドを必須とします。空の値を送信した場合も、フィールドが指定されたものとして扱われます。

デフォルト値なしで | None を使用した場合、パラメータは必須のままです。

追加のフィールドはモデルの extra 設定に従います。無視された追加フィールドによって、モデルが指定されたことにはなりません。

チェックされていないチェックボックスのみを含むオプションのモデルは省略されます。明示的に指定するには、enabled=false などの値を送信してください。

フォームモデルのオプションの Union

モデルのオプションの Union も使用できます。

from typing import Annotated, Literal

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Person(BaseModel):
    kind: Literal["person"]
    name: str


class Company(BaseModel):
    kind: Literal["company"]
    company_name: str


@app.post("/contacts/")
async def create_contact(
    contact: Annotated[Person | Company | None, Form(discriminator="kind")] = None,
):
    return contact
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

from typing import Literal

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Person(BaseModel):
    kind: Literal["person"]
    name: str


class Company(BaseModel):
    kind: Literal["company"]
    company_name: str


@app.post("/contacts/")
async def create_contact(
    contact: Person | Company | None = Form(default=None, discriminator="kind"),
):
    return contact

フォームフィールドが送信されない場合、contact は None になります。kind=person と name、または kind=company と company_name を送信してください。判別子によって検証するモデルが選択されます。

フォームモデルの Union の組み合わせ

Union を他のフォームモデル、個別の Form パラメータ、File パラメータと組み合わせることができます。各 Union は個別に検証されます。

from typing import Annotated, Literal

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Person(BaseModel):
    contact_type: Literal["person"]
    name: str


class Company(BaseModel):
    contact_type: Literal["company"]
    company_name: str


class Email(BaseModel):
    notification_type: Literal["email"]
    email: str


class SMS(BaseModel):
    notification_type: Literal["sms"]
    phone: str


@app.post("/contacts/")
async def create_contact(
    contact: Annotated[Person | Company, Form(discriminator="contact_type")],
    token: Annotated[str, Form()],
    notification: Annotated[
        Email | SMS | None, Form(discriminator="notification_type")
    ] = None,
):
    return {"contact": contact, "notification": notification, "token": token}
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

from typing import Literal

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Person(BaseModel):
    contact_type: Literal["person"]
    name: str


class Company(BaseModel):
    contact_type: Literal["company"]
    company_name: str


class Email(BaseModel):
    notification_type: Literal["email"]
    email: str


class SMS(BaseModel):
    notification_type: Literal["sms"]
    phone: str


@app.post("/contacts/")
async def create_contact(
    contact: Person | Company = Form(discriminator="contact_type"),
    token: str = Form(),
    notification: Email | SMS | None = Form(
        default=None, discriminator="notification_type"
    ),
):
    return {"contact": contact, "notification": notification, "token": token}

すべてのフィールドを同じフォームで送信してください。例えば、contact_type=person、name=Rick、notification_type=email、email=rick@example.com、token=secret を送信します。

notification を None のままにするには、通知用のフィールドを省略してください。

フォームモデルとファイル

フォームモデルをファイルパラメータと組み合わせることもできます。

from typing import Annotated

from fastapi import FastAPI, Form, UploadFile
from pydantic import BaseModel

app = FastAPI()


class Profile(BaseModel):
    name: str


@app.post("/profiles/")
async def create_profile(profile: Annotated[Profile, Form()], avatar: UploadFile):
    return {"profile": profile, "filename": avatar.filename}
🤓 Other versions and variants

Tip

Prefer to use the Annotated version if possible.

from fastapi import FastAPI, File, Form, UploadFile
from pydantic import BaseModel

app = FastAPI()


class Profile(BaseModel):
    name: str


@app.post("/profiles/")
async def create_profile(profile: Profile = Form(), avatar: UploadFile = File()):
    return {"profile": profile, "filename": avatar.filename}

ここでは、クライアントは multipart/form-data を使用して name フィールドと avatar ファイルを送信します。

まとめ

FastAPI でフォームフィールドを宣言するために Pydantic モデルを使用できます。😎