開発

Pydantic Fieldの使い方:default_factory・alias・制約の引数一覧とOpenAPIへの反映【2.14対応】

Pydantic Fieldの使い方:default_factory・alias・制約の引数一覧とOpenAPIへの反映【2.14対応】

PydanticのField()は、BaseModelの各フィールドに既定値、入力の制約、別名、説明文を付けるための関数です。quantity: int = Field(gt=0, le=100)と書けば、0以下や101以上の値はValidationErrorになり、同じ条件がJSON SchemaのexclusiveMinimumとmaximumにも出力されます。この記事では、2026年11月時点の最新版pydantic 2.14.3で指定できる引数を用途別に整理し、default_factoryとaliasの使い分け、Annotatedでの使い回し、FastAPIのSwagger UIへの反映までをコードで示す構成です。v1のregexやmin_itemsがv2で出すエラーと置き換え先も扱います。

まとめ:Pydantic Fieldで押さえる引数と書き方の結論

Field()の引数は30を超えますが、実務で毎回使うのはdefault_factory、gt・geなどの数値制約、max_length、pattern、alias、descriptionの6つです。残りは必要になった場面で引けば足ります。

呼び出すたびに値が変わる既定値(現在時刻やUUID)はdefault_factoryで渡します。同じ制約を複数のモデルで使うなら、Annotated[int, Field(gt=0)]を型エイリアスにして1か所で定義するのが保守しやすい形です。

Fieldに書いた制約と説明は、そのままJSON SchemaとFastAPIのOpenAPIに出力されます。API仕様書を手で書かず、モデルの定義を仕様の正本にできる点がFieldを書く一番の理由です。

Pydantic 2.14のField()で指定できる引数一覧と書き方の基本形

まずは引数の全体像と、Fieldを書く2通りの形式を確認します。PyPIのpydanticでは、2026年11月14日時点の最新が2.14.3(11月5日公開・Python 3.10以上)です。

Field()の引数を用途別に整理した一覧表と最初に覚える6つ

v2.14.3のfields.pyにあるField()の定義から、引数を用途で分けると次の表になります。

用途 主な引数
既定値 default、default_factory
数値の制約 gt、ge、lt、le、multiple_of
長さの制約 min_length、max_length
正規表現 pattern
小数の制約 max_digits、decimal_places
別名(入出力) alias
別名(入力のみ) validation_alias
別名(出力のみ) serialization_alias
スキーマの説明 description、examples
出力の制御 exclude、exclude_if、repr
型と変更の制御 strict、frozen
既定値の検証 validate_default
廃止の告知 deprecated

このほか、項目名を表すtitle、dataclass向けのinit・kw_only、Unionの判別に使うdiscriminatorがあります。最初に覚えるのは、まとめで挙げた6つで十分です。BaseModelそのものの書き方はPydanticの読み方・BaseModelの使い方とV1との違いで整理しています。

代入形式とAnnotated形式の違いと型エイリアスで制約を使い回す方法

Fieldはname: str = Field(max_length=50)の代入形式と、Annotatedの中に置く形式のどちらでも書けます。公式のFieldsの解説が勧めるのはAnnotated形式です。代入形式は既定値があるように見えるうえ、制約を型として切り出せません。

from typing import Annotated

from pydantic import BaseModel, Field

# 制約を型エイリアスにまとめ、複数のモデルで使い回す
ProductCode = Annotated[str, Field(pattern=r"^[A-Z]{3}-\d{4}$", examples=["ABC-0001"])]
Quantity = Annotated[int, Field(gt=0, le=100)]


class OrderLine(BaseModel):
    code: ProductCode
    quantity: Quantity = 1  # 既定値は代入で書く


class StockMove(BaseModel):
    code: ProductCode
    quantity: Quantity

注意点は1つです。default・default_factory・aliasの3つは、mypyなどの型チェッカーが__init__の引数を判断する材料に使うため、Annotatedの中ではなく代入形式で書きます。Annotatedの中に書いても実行はできますが、エディタが必須項目と誤認して警告を出します。

default_factoryと既定値で必須・任意を決める書き方と落とし穴

既定値の書き方は、そのフィールドが必須か任意かを決めます。ここで誤ると、APIが受け付けるべき入力を422で弾く不具合になります。

Field(…)とNone既定値の違いとOptionalだけでは任意にならない理由

既定値を書かないフィールドは必須です。Field(...)の...(Ellipsis)も必須を表しますが、既定値を省略した場合と意味は同じで、今は書く必要がありません。

見落としやすいのはstr | Noneの扱いです。v1ではOptional[str]と書くだけで既定値Noneの任意項目になりましたが、v2では「Noneも受け付ける必須項目」になります。項目ごと省略させたいならnickname: str | None = Noneと既定値まで書きます。

既定値そのものは、既定では検証されません。v2.14.3のconfig.pyでもvalidate_defaultの既定はFalseです。port: int = "abc"と書いても起動時には止まらないため、既定値も検証したいフィールドにはField(validate_default=True)を付けます。

datetime.nowをdefault_factoryで渡す理由とリスト既定値の扱い

created_at: datetime = datetime.now()と書くと、datetime.now()はクラスを定義した瞬間に1回だけ実行されます。その後に作るインスタンスはすべて同じ時刻を持ち、ログの作成日時がプロセスの起動時刻で揃う、という不具合になります。

from datetime import datetime
from uuid import UUID, uuid4

from pydantic import BaseModel, Field


class Order(BaseModel):
    id: UUID = Field(default_factory=uuid4)
    created_at: datetime = Field(default_factory=datetime.now)
    tags: list[str] = []  # 可変の既定値でもインスタンスごとに複製される

default_factoryには呼び出す関数そのもの(括弧なしのdatetime.now)を渡します。インスタンスを作るたびに呼ばれ、毎回新しい値が入ります。

一方、tags: list[str] = []はPydanticでは問題になりません。公式ドキュメントのとおり、ハッシュできない既定値はインスタンスごとにディープコピーされるためです。dataclassの感覚でdefault_factory=listと書いても動きますが、必須ではありません。

2.10で入った検証済みデータを受け取るdefault_factoryの書き方

v2.10から、default_factoryは引数を1つ取る関数も受け付けます。引数には、そのフィールドより前に定義され、検証を通ったフィールドの値が辞書で渡されます。

from pydantic import BaseModel, Field


class Member(BaseModel):
    user_name: str
    # 表示名が送られてこなければ、検証済みの user_name から作る
    display_name: str = Field(default_factory=lambda data: data["user_name"].title())


print(Member(user_name="taro yamada").display_name)
# Taro Yamada

参照できるのは定義順で前にあるフィールドだけです。display_nameをuser_nameより上に書くと、辞書にキーが無くKeyErrorになります。複数のフィールドを見比べて値を決める処理や、入力の矛盾を弾く処理はmodel_validatorの役割です。既定値を埋めるだけの用途に絞って使います。

数値・文字列・リストの制約引数とJSON Schemaへの変換規則

制約引数は入力の検証に使われるだけでなく、JSON Schemaのキーワードにも変換されます。対応を知っておくと、フロントエンドとAPIで同じ条件を共有できます。

gt・ge・max_length・patternの対応表と違反時のエラー種別

v2.14.3のjson_schema.pyにある変換表を、型ごとにまとめると次のとおりです。

Fieldの引数 対象の型 JSON Schema
gt int・float exclusiveMinimum
ge int・float minimum
lt int・float exclusiveMaximum
le int・float maximum
multiple_of int・float multipleOf
min_length str minLength
min_length list minItems
max_length dict maxProperties
pattern str pattern

同じmin_lengthでも、文字列ならminLength、リストならminItemsに変わります。先ほどのOrderLineで確かめると、次の出力になります。

print(OrderLine.model_json_schema()["properties"]["quantity"])
# {'default': 1, 'exclusiveMinimum': 0, 'maximum': 100, 'title': 'Quantity', 'type': 'integer'}

違反したときのエラー種別は、gtならgreater_than、max_length(文字列)ならstring_too_longです。種別で分岐して日本語のメッセージに置き換えると、422の応答を利用者向けに整えられます。JSON Schemaのキーワード自体の意味はJSON Schemaの書き方とDraft 2020-12の解説にまとめています。

patternで先読みが使えないrust-regexとpython-reの切り替え

patternの正規表現は、既定ではPythonのreではなくRust製のエンジンで評価されます。config.pyの説明では、既定のrust-regexはバックトラックをしないためReDoS(正規表現による処理遅延の攻撃)に強い一方、一部の構文を持ちません。代表例が先読みの(?=...)です。

from pydantic import BaseModel, ConfigDict, Field


class PasswordInput(BaseModel):
    # 先読みを使うため re モジュールに切り替える(v2.5以上)
    model_config = ConfigDict(regex_engine="python-re")

    # 英字と数字を両方含む8文字以上
    value: str = Field(pattern=r"^(?=.*[A-Za-z])(?=.*\d).{8,}$")

切り替えはモデル単位です。外部から受け取る文字列にpython-reを使うと、書き方次第で処理時間が入力長に対して急増するため、先読みが要る1つのモデルだけに限定します。先読みを使わず「英字を含む」「数字を含む」をfield_validatorで2回判定する方が、エンジンを切り替えずに済む分だけ安全です。

3種類のaliasでcamelCaseのJSONを受け取り返す実装と注意点

外部APIやフロントエンドがcamelCaseで値をやり取りする一方、Pythonのコードはsnake_caseで書きたい、という場面で使うのがaliasです。

検証用と出力用でaliasを分けるAliasChoicesとAliasPathの書き方

aliasは3種類あります。aliasは入力と出力の両方、validation_aliasは入力だけ、serialization_aliasは出力だけに効きます。fields.pyでは、aliasだけを指定すると残り2つに同じ値が入る作りです。

from pydantic import AliasChoices, AliasPath, BaseModel, Field


class Customer(BaseModel):
    customer_id: int = Field(alias="customerId")
    # 旧APIの "name" と新APIの "fullName" のどちらでも受け取る
    full_name: str = Field(validation_alias=AliasChoices("fullName", "name"))
    # {"address": {"zip": "..."}} の入れ子から取り出す
    zip_code: str = Field(validation_alias=AliasPath("address", "zip"))


c = Customer.model_validate(
    {"customerId": 1, "name": "山田", "address": {"zip": "100-0001"}}
)
print(c.model_dump())
# {'customer_id': 1, 'full_name': '山田', 'zip_code': '100-0001'}
print(c.model_dump(by_alias=True))
# {'customerId': 1, 'full_name': '山田', 'zip_code': '100-0001'}

AliasChoicesは左から順に探し、最初に見つかったキーを使います。APIのキー名を移行する期間に、新旧どちらの名前でも受け付けたいときに適した指定です。aliasを付けると、既定ではCustomer(customer_id=1)のようにフィールド名で値を渡せなくなります。両方を許すvalidate_by_nameや、全フィールドをまとめてcamelCaseにするalias_generatorはConfigDictの設定一覧で扱っています。

model_dumpのby_alias既定値とFastAPIの応答で名前が変わる仕組み

上の出力のとおり、model_dump()は既定(by_alias=False)ではフィールド名で出力します。ところがFastAPIの応答では、同じモデルがalias名で返ります。FastAPI 0.162.7のrouting.pyで、パス操作のresponse_model_by_aliasの既定がTrueだからです。

この差が原因で、「テストでmodel_dump()した結果とAPIの応答のキー名が違う」という食い違いが起きます。APIの応答と同じ形を得たいテストで使うのは、model_dump(by_alias=True)です。モデル全体で既定を変えるなら、ConfigDictのserialize_by_alias=Trueを指定します。

環境変数名を変える用途でもvalidation_aliasは使えます。pydantic-settingsでenv_prefixと組み合わせたときの挙動はpydantic-settingsの使い方で説明しています。

FastAPIのSwagger UIへFieldの説明と例を反映する書き方

FastAPIでは、リクエストとレスポンスのモデルに書いたFieldの情報が、そのまま/openapi.jsonとSwagger UIに出ます。

descriptionとexamplesをOpenAPIのスキーマへ載せる手順

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class ItemIn(BaseModel):
    name: str = Field(min_length=1, max_length=50, description="商品名", examples=["USBケーブル"])
    price: int = Field(ge=0, description="税抜価格(円)", examples=[1280])
    legacy_code: str | None = Field(default=None, deprecated="v2 APIで廃止予定。codeを使う")


@app.post("/items")
def create_item(item: ItemIn) -> ItemIn:
    return item

起動して/openapi.jsonを開くと、components.schemas.ItemInの各プロパティにdescription、examples、maxLengthなどが並び、legacy_codeには"deprecated": trueが付きます。FastAPI公式のリクエスト例の解説によると、FastAPI 0.99.0以降はOpenAPI 3.1.0を出力し、examplesはJSON Schemaのリストとして入ります。

descriptionは人が読むためだけの欄ではありません。LLMに構造化出力をさせる際には、モデルのJSON Schemaが指示として渡り、descriptionが各項目の意味の説明になります。その仕組みはLLM構造化出力の仕組みと実装で解説しています。Swagger UI側の表示の調整はFastAPIのSwagger UIの使い方が詳しいです。

deprecatedとexclude_ifで廃止予定の項目と出力を制御する方法

deprecated(v2.7.0追加)を付けたフィールドは、JSON Schemaにdeprecatedが付くほか、Pythonのコードから値を読んだときにDeprecationWarningが出ます。APIの利用者とアプリの開発者の両方に、廃止予定を1か所の記述で伝えられます。

from pydantic import BaseModel, Field


class UserOut(BaseModel):
    name: str
    password_hash: str = Field(exclude=True, repr=False)  # 出力にもログにも出さない
    nickname: str | None = Field(default=None, exclude_if=lambda v: v is None)


print(UserOut(name="taro", password_hash="xxx").model_dump())
# {'name': 'taro'}

exclude=Trueは常に出力から外し、repr=Falseはprintやログの表示から外します。v2.12で加わったexclude_ifは、値を見て外すかを決める引数です。「Noneのときだけキーごと省く」応答を、model_dump(exclude_none=True)を呼ぶ側に頼らずモデルの定義で保証できます。

v1のFieldからの移行とField()を使わずに済ませる場面の判断

古い記事やv1時代のコードをそのまま貼ると、Fieldの引数でエラーや警告が出ます。最後に移行の対応と、Fieldを書かない判断を整理します。

regex・min_items・constなどv1の引数が出すエラーと置き換え先

v2.14.3のfields.pyでは、v1の引数を「エラーで止めるもの」と「警告を出して読み替えるもの」に分けています。

v1の引数 2.14.3の挙動 置き換え先
regex エラー pattern
const エラー Literal型
unique_items エラー set型
min_items・max_items 警告 min_length・max_length
allow_mutation 警告 frozen
上記以外の任意キー 警告 json_schema_extra

厄介なのは最後の行です。Field(minimum=0)のように存在しない引数を渡すと、警告とともにjson_schema_extraへ入り、スキーマにはminimumが出ます。それでも入力の検証は一切行われません。警告を見落とすと「仕様書には下限があるのに負の値が通る」状態になるため、移行時は-W error::DeprecationWarningを付けてテストを走らせ、警告をエラーとして洗い出します。

Field()を書かず型ヒントだけで足りる場面と書くべき場面の線引き

すべてのフィールドにFieldを書く必要はありません。社内のバッチが読む設定ファイルのように、入力元が自分たちのコードだけなら、intやstrの型ヒントで十分です。

Fieldを書くべきなのは、次のどれかに当たるフィールドです。優先度が一番高いのは1つ目です。

  • 外部から受け取る値で、範囲や長さの上限がある(金額、件数、自由入力の文字列)
  • キー名がPythonの命名と異なる(camelCase、旧APIの名前)
  • OpenAPIで他チームや取引先に仕様として公開する
  • 出力やログに出してはいけない値を含む

自由入力の文字列にmax_lengthが無いと、数MBの文字列がそのままDBや外部APIまで届きます。既存のAPIの入力検証を見直し、Pydanticのモデルを仕様の正本にする作り直しは、API開発・システム連携の受託開発でご相談を受け付けています。

SQLModelのFieldやFastAPIのQueryと混同しやすい違いと見分け方

名前が似た別物が2つあります。1つはfrom sqlmodel import Fieldで、primary_keyやforeign_keyなどテーブル定義の引数を持つSQLModel独自の関数です。PydanticのFieldとimport元を取り違えると、primary_keyが無いというエラーになります。使い方はSQLModelのモデル定義とFastAPI連携を参照してください。

もう1つはFastAPIのQuery・Path・Bodyです。どれもPydanticのFieldInfoを継承しているため、max_lengthやgeなど同じ名前の制約を使えます。ボディのモデルの中ではPydanticのField、関数の引数ではFastAPIのQueryなどと覚えれば迷いません。POSTのボディで422が返ったときの読み方はFastAPIでPOSTを受け取る方法と422の直し方で扱っています。

よくある質問

Pydantic Fieldについて出てくることの多い質問に、2026年11月時点の版(pydantic 2.14.3、FastAPI 0.162.7)で答えます。

Fieldとfield_validatorはどう使い分けますか?

Fieldの制約引数で表せる条件はFieldに書き、表せない条件だけをfield_validatorで書きます。範囲、長さ、正規表現はFieldで足り、JSON Schemaにも出力されます。一方、「全角カナのみ」「祝日を除く日付」のような業務ルールや、値の正規化(前後の空白を除くなど)はバリデータの役割です。バリデータに書いた条件はJSON Schemaに出ないため、API仕様書に載せたい条件はできるだけFieldで表します。

Fieldで設定した制約をあとから確認する方法はありますか?

2通りあります。Model.model_fields["quantity"]でFieldInfoを取り出すと、metadataに制約、aliasやdescriptionに各値が入っています。v2.11からmodel_fieldsはインスタンスではなくクラスから読む形が推奨で、インスタンス経由は非推奨です。外部に渡すならModel.model_json_schema()でJSON Schemaとして取り出す方が扱いやすいでしょう。

Annotatedの中にdefaultを書いてもよいですか?

実行時は動作しますが、勧めません。mypyやPyrightなどの型チェッカーは、Annotatedの中のFieldを見ずに__init__の引数を判断します。そのため既定値がある項目でも必須と判定され、呼び出し側に誤った警告が出ます。quantity: Annotated[int, Field(gt=0)] = 1のように、制約はAnnotatedの中、既定値は代入で書き分けるのが公式ドキュメントの推奨です。

Field(frozen=True)とConfigDictのfrozenは何が違いますか?

両者の違いは、書き換えを禁止する対象の範囲です。Field(frozen=True)はそのフィールドだけを書き換え禁止にし、代入時のエラーはfrozen_fieldのValidationErrorです。ConfigDict(frozen=True)はモデルのすべてのフィールドを書き換え禁止にし、インスタンスをハッシュ可能にします。IDや作成日時だけを固定し、他は更新させたいモデルなら、フィールド単位のfrozenを選びます。

strict=Trueを付けるとどのような入力が弾かれますか?

既定のlaxモードでは、intのフィールドに文字列の"10"が来ると整数の10に変換して受け付けます。Field(strict=True)を付けたフィールドではこの変換をせず、型が違えばエラーにする設定です。ただしJSONを入力にした場合、日付やUUIDなどJSONに専用の型が無い値は、strictでも文字列から変換されます。金額のように取り違えると損害が大きい数値だけに付けるのが現実的です。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.11.11 テックブログ Microsoft 2026年11月の月例更新:Outlookの悪用済みCVE-2026-124194と修正版の確認方法
  2. 2026.11.13 テックブログ 宝くじ公式サイト不正アクセス:4,436名の対象確認とパスワード再設定、変更された仕組み
  3. 2026.11.13 テックブログ ignica不正アクセス:6万4,003件の氏名とメールアドレス流出の可能性と対象確認
  4. 2026.11.13 テックブログ KARTEの不正アクセス:全機能停止で利用企業がタグ・配信・データ連携を確かめる手順
  5. 2026.11.11 テックブログ JANISの不正アクセス:最大11,652名の医療機関情報と情報窃取プログラムの検知設定

RELATED POSTS 関連記事

目次