Files
fastapi/docs/ja/docs/tutorial/extra-models.md
2025-12-21 18:13:20 +00:00

8.3 KiB
Raw Blame History

Extra Models

先ほどの例に続き、複数の関連モデルを持つことは一般的です。

これはユーザーモデルの場合は特にそうです。なぜなら:

  • 入力モデル にはパスワードが必要です。
  • 出力モデルはパスワードをもつべきではありません。
  • データベースモデルはおそらくハッシュ化されたパスワードが必要になるでしょう。

/// danger

ユーザーの平文のパスワードは絶対に保存しないでください。常に検証できる「安全なハッシュ」を保存してください。

知らない方は、セキュリティの章{.internal-link target=_blank}で「パスワードハッシュ」とは何かを学ぶことができます。

///

Multiple models

ここでは、パスワードフィールドをもつモデルがどのように見えるのか、また、どこで使われるのか、大まかなイメージを紹介します:

{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}

About **user_in.model_dump()

Pydanticの.model_dump()

user_inUserInクラスのPydanticモデルです。

Pydanticモデルには、モデルのデータを含むdictを返す.model_dump()メソッドがあります。

そこで、以下のようなPydanticオブジェクトuser_inを作成すると:

user_in = UserIn(username="john", password="secret", email="john.doe@example.com")

そして呼び出すと:

user_dict = user_in.model_dump()

これで変数user_dictのデータを持つdictができました。これはPydanticモデルのオブジェクトの代わりにdictです)。

そして呼び出すと:

print(user_dict)

以下のようなPythonのdictを得ることができます:

{
    'username': 'john',
    'password': 'secret',
    'email': 'john.doe@example.com',
    'full_name': None,
}

dictの展開

user_dictのようなdictを受け取り、それを**user_dictを持つ関数またはクラスに渡すと、Pythonはそれを「展開」します。これはuser_dictのキーと値を直接キー・バリューの引数として渡します。

そこで上述のuser_dictの続きを以下のように書くと:

UserInDB(**user_dict)

以下と同等の結果になります:

UserInDB(
    username="john",
    password="secret",
    email="john.doe@example.com",
    full_name=None,
)

もっと正確に言えば、user_dictを将来的にどんな内容であっても直接使用することになります:

UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
)

別のモデルの内容からつくるPydanticモデル

上述の例ではuser_in.model_dump()からuser_dictをこのコードのように取得していますが:

user_dict = user_in.model_dump()
UserInDB(**user_dict)

これは以下と同等です:

UserInDB(**user_in.model_dump())

...なぜならuser_in.model_dump()dictであり、**を付与してUserInDBを渡してPythonに「展開」させているからです。

そこで、別のPydanticモデルのデータからPydanticモデルを取得します。

dictの展開と追加キーワード

そして、追加のキーワード引数hashed_password=hashed_passwordを以下のように追加すると:

UserInDB(**user_in.model_dump(), hashed_password=hashed_password)

...以下のようになります:

UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
    hashed_password = hashed_password,
)

/// warning

追加のサポート関数fake_password_hasherfake_save_userは、データの可能な流れをデモするだけであり、もちろん本当のセキュリティを提供しているわけではありません。

///

Reduce duplication

コードの重複を減らすことは、FastAPIの中核的なアイデアの1つです。

コードの重複が増えると、バグやセキュリティの問題、コードの非同期化問題(ある場所では更新しても他の場所では更新されない場合)などが発生する可能性が高くなります。

そして、これらのモデルは全てのデータを共有し、属性名や型を重複させています。

もっと良い方法があります。

他のモデルのベースとなるUserBaseモデルを宣言することができます。そして、そのモデルの属性(型宣言、検証など)を継承するサブクラスを作ることができます。

データの変換、検証、文書化などはすべて通常通りに動作します。

このようにして、モデル間の違いだけを宣言することができます(平文のpasswordhashed_password、パスワードなし):

{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}

Union or anyOf

レスポンスを2つ以上の型のUnionとして宣言できます。つまり、そのレスポンスはそれらのいずれかになります。

OpenAPIではanyOfで定義されます。

そのためには、標準的なPythonの型ヒントtyping.Unionを使用します:

/// note | 備考

Unionを定義する場合は、最も具体的な型を先に、その後により具体性の低い型を含めてください。以下の例では、より具体的なPlaneItemUnion[PlaneItem, CarItem]内でCarItemより前に来ています。

///

{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}

Python 3.10のUnion

この例では、引数response_modelの値としてUnion[PlaneItem, CarItem]を渡しています。

型アノテーションに書くのではなく、引数の値として渡しているため、Python 3.10でもUnionを使う必要があります。

型アノテーションであれば、次のように縦棒を使用できました:

some_variable: PlaneItem | CarItem

しかし、これを代入でresponse_model=PlaneItem | CarItemのように書くと、Pythonはそれを型アテーションとして解釈するのではなく、PlaneItemCarItemの間で無効な操作を行おうとしてしまうため、エラーになります。

List of models

同じように、オブジェクトのリストのレスポンスを宣言できます。

そのためには、標準のPythonのtyping.ListまたはPython 3.9以降では単にlist)を使用します:

{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}

Response with arbitrary dict

また、Pydanticモデルを使用せずに、キーと値の型だけを定義した任意のdictを使ってレスポンスを宣言することもできます。

これは、有効なフィールド・属性名Pydanticモデルに必要なものを事前に知らない場合に便利です。

この場合、typing.DictまたはPython 3.9以降では単にdict)を使用できます:

{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}

Recap

複数のPydanticモデルを使用し、ケースごとに自由に継承します。

エンティティが異なる「状態」を持たなければならない場合は、エンティティごとに単一のデータモデルを持つ必要はありません。passwordpassword_hash、パスワードなしを含む状態を持つユーザー「エンティティ」の場合と同様です。