6.7 KiB
モデル - より詳しく
先ほどの例に続き、複数の関連モデルを持つことが一般的です。
これはユーザーモデルの場合は特にそうです。なぜなら:
- 入力モデル にはパスワードが必要です。
- 出力モデルはパスワードをもつべきではありません。
- データベースモデルはおそらくハッシュ化されたパスワードが必要になるでしょう。
/// danger | "危険"
ユーザーの平文のパスワードは絶対に保存しないでください。常に認証に利用可能な「安全なハッシュ」を保存してください。
知らない方は、セキュリティの章{.internal-link target=_blank}で「パスワードハッシュ」とは何かを学ぶことができます。
///
複数のモデル
ここでは、パスワードフィールドをもつモデルがどのように見えるのか、また、どこで使われるのか、大まかなイメージを紹介します:
{!../../docs_src/extra_models/tutorial001.py!}
**user_in.dict()
について
Pydanticの.dict()
user_in
はUserIn
クラスのPydanticモデルです。
Pydanticモデルには、モデルのデータを含むdict
を返す.dict()
メソッドがあります。
そこで、以下のようなPydanticオブジェクトuser_in
を作成すると:
user_in = UserIn(username="john", password="secret", email="[email protected]")
そして呼び出すと:
user_dict = user_in.dict()
これで変数user_dict
のデータを持つdict
ができました。(これはPydanticモデルのオブジェクトの代わりにdict
です)。
そして呼び出すと:
print(user_dict)
以下のようなPythonのdict
を得ることができます:
{
'username': 'john',
'password': 'secret',
'email': '[email protected]',
'full_name': None,
}
dict
の展開
user_dict
のようなdict
を受け取り、それを**user_dict
を持つ関数(またはクラス)に渡すと、Pythonはそれを「展開」します。これはuser_dict
のキーと値を直接キー・バリューの引数として渡します。
そこで上述のuser_dict
の続きを以下のように書くと:
UserInDB(**user_dict)
以下と同等の結果になります:
UserInDB(
username="john",
password="secret",
email="[email protected]",
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.dict()
からuser_dict
をこのコードのように取得していますが:
user_dict = user_in.dict()
UserInDB(**user_dict)
これは以下と同等です:
UserInDB(**user_in.dict())
...なぜならuser_in.dict()
はdict
であり、**
を付与してUserInDB
を渡してPythonに「展開」させているからです。
そこで、別のPydanticモデルのデータからPydanticモデルを取得します。
dict
の展開と追加引数
そして、追加のキーワード引数hashed_password=hashed_password
を以下のように追加すると:
UserInDB(**user_in.dict(), 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 | "注意"
サポートしている追加機能は、データの可能な流れをデモするだけであり、もちろん本当のセキュリティを提供しているわけではありません。
///
重複の削減
コードの重複を減らすことは、FastAPIの中核的なアイデアの1つです。
コードの重複が増えると、バグやセキュリティの問題、コードの非同期化問題(ある場所では更新しても他の場所では更新されない場合)などが発生する可能性が高くなります。
そして、これらのモデルは全てのデータを共有し、属性名や型を重複させています。
もっと良い方法があります。
他のモデルのベースとなるUserBase
モデルを宣言することができます。そして、そのモデルの属性(型宣言、検証など)を継承するサブクラスを作ることができます。
データの変換、検証、文書化などはすべて通常通りに動作します。
このようにして、モデル間の違いだけを宣言することができます:
{!../../docs_src/extra_models/tutorial002.py!}
Union
またはanyOf
レスポンスを2つの型のUnion
として宣言することができます。
OpenAPIではanyOf
で定義されます。
そのためには、標準的なPythonの型ヒントtyping.Union
を使用します:
{!../../docs_src/extra_models/tutorial003.py!}
モデルのリスト
同じように、オブジェクトのリストのレスポンスを宣言することができます。
そのためには、標準のPythonのtyping.List
を使用する:
{!../../docs_src/extra_models/tutorial004.py!}
任意のdict
を持つレスポンス
また、Pydanticモデルを使用せずに、キーと値の型だけを定義した任意のdict
を使ってレスポンスを宣言することもできます。
これは、有効なフィールド・属性名(Pydanticモデルに必要なもの)を事前に知らない場合に便利です。
この場合、typing.Dict
を使用することができます:
{!../../docs_src/extra_models/tutorial005.py!}
まとめ
複数のPydanticモデルを使用し、ケースごとに自由に継承します。
エンティティが異なる「状態」を持たなければならない場合は、エンティティごとに単一のデータモデルを持つ必要はありません。password
や password_hash
やパスワードなしなどのいくつかの「状態」をもつユーザー「エンティティ」の場合の様にすれば良いです。