|
| 1 | +# モデル - より詳しく |
| 2 | + |
| 3 | +先ほどの例に続き、複数の関連モデルを持つことが一般的です。 |
| 4 | + |
| 5 | +これはユーザーモデルの場合は特にそうです。なぜなら: |
| 6 | + |
| 7 | +* **入力モデル** にはパスワードが必要です。 |
| 8 | +* **出力モデル**はパスワードをもつべきではありません。 |
| 9 | +* **データベースモデル**はおそらくハッシュ化されたパスワードが必要になるでしょう。 |
| 10 | + |
| 11 | +!!! danger "危険" |
| 12 | + ユーザーの平文のパスワードは絶対に保存しないでください。常に認証に利用可能な「安全なハッシュ」を保存してください。 |
| 13 | + |
| 14 | + 知らない方は、[セキュリティの章](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}で「パスワードハッシュ」とは何かを学ぶことができます。 |
| 15 | + |
| 16 | +## 複数のモデル |
| 17 | + |
| 18 | +ここでは、パスワードフィールドをもつモデルがどのように見えるのか、また、どこで使われるのか、大まかなイメージを紹介します: |
| 19 | + |
| 20 | +```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" |
| 21 | +{!../../../docs_src/extra_models/tutorial001.py!} |
| 22 | +``` |
| 23 | + |
| 24 | +### `**user_in.dict()`について |
| 25 | + |
| 26 | +#### Pydanticの`.dict()` |
| 27 | + |
| 28 | +`user_in`は`UserIn`クラスのPydanticモデルです。 |
| 29 | + |
| 30 | +Pydanticモデルには、モデルのデータを含む`dict`を返す`.dict()`メソッドがあります。 |
| 31 | + |
| 32 | +そこで、以下のようなPydanticオブジェクト`user_in`を作成すると: |
| 33 | + |
| 34 | +```Python |
| 35 | +user_in = UserIn( username="john", password="secret", email="[email protected]") |
| 36 | +``` |
| 37 | + |
| 38 | +そして呼び出すと: |
| 39 | + |
| 40 | +```Python |
| 41 | +user_dict = user_in.dict() |
| 42 | +``` |
| 43 | + |
| 44 | +これで変数`user_dict`のデータを持つ`dict`ができました。(これはPydanticモデルのオブジェクトの代わりに`dict`です)。 |
| 45 | + |
| 46 | +そして呼び出すと: |
| 47 | + |
| 48 | +```Python |
| 49 | +print(user_dict) |
| 50 | +``` |
| 51 | + |
| 52 | +以下のようなPythonの`dict`を得ることができます: |
| 53 | + |
| 54 | +```Python |
| 55 | +{ |
| 56 | + 'username': 'john', |
| 57 | + 'password': 'secret', |
| 58 | + |
| 59 | + 'full_name': None, |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +#### `dict`の展開 |
| 64 | + |
| 65 | +`user_dict`のような`dict`を受け取り、それを`**user_dict`を持つ関数(またはクラス)に渡すと、Pythonはそれを「展開」します。これは`user_dict`のキーと値を直接キー・バリューの引数として渡します。 |
| 66 | + |
| 67 | +そこで上述の`user_dict`の続きを以下のように書くと: |
| 68 | + |
| 69 | +```Python |
| 70 | +UserInDB(**user_dict) |
| 71 | +``` |
| 72 | + |
| 73 | +以下と同等の結果になります: |
| 74 | + |
| 75 | +```Python |
| 76 | +UserInDB( |
| 77 | + username="john", |
| 78 | + password="secret", |
| 79 | + |
| 80 | + full_name=None, |
| 81 | +) |
| 82 | +``` |
| 83 | + |
| 84 | +もっと正確に言えば、`user_dict`を将来的にどんな内容であっても直接使用することになります: |
| 85 | + |
| 86 | +```Python |
| 87 | +UserInDB( |
| 88 | + username = user_dict["username"], |
| 89 | + password = user_dict["password"], |
| 90 | + email = user_dict["email"], |
| 91 | + full_name = user_dict["full_name"], |
| 92 | +) |
| 93 | +``` |
| 94 | + |
| 95 | +#### 別のモデルからつくるPydanticモデル |
| 96 | + |
| 97 | +上述の例では`user_in.dict()`から`user_dict`をこのコードのように取得していますが: |
| 98 | + |
| 99 | +```Python |
| 100 | +user_dict = user_in.dict() |
| 101 | +UserInDB(**user_dict) |
| 102 | +``` |
| 103 | + |
| 104 | +これは以下と同等です: |
| 105 | + |
| 106 | +```Python |
| 107 | +UserInDB(**user_in.dict()) |
| 108 | +``` |
| 109 | + |
| 110 | +...なぜなら`user_in.dict()`は`dict`であり、`**`を付与して`UserInDB`を渡してPythonに「展開」させているからです。 |
| 111 | + |
| 112 | +そこで、別のPydanticモデルのデータからPydanticモデルを取得します。 |
| 113 | + |
| 114 | +#### `dict`の展開と追加引数 |
| 115 | + |
| 116 | +そして、追加のキーワード引数`hashed_password=hashed_password`を以下のように追加すると: |
| 117 | + |
| 118 | +```Python |
| 119 | +UserInDB(**user_in.dict(), hashed_password=hashed_password) |
| 120 | +``` |
| 121 | + |
| 122 | +...以下のようになります: |
| 123 | + |
| 124 | +```Python |
| 125 | +UserInDB( |
| 126 | + username = user_dict["username"], |
| 127 | + password = user_dict["password"], |
| 128 | + email = user_dict["email"], |
| 129 | + full_name = user_dict["full_name"], |
| 130 | + hashed_password = hashed_password, |
| 131 | +) |
| 132 | +``` |
| 133 | + |
| 134 | +!!! warning "注意" |
| 135 | + サポートしている追加機能は、データの可能な流れをデモするだけであり、もちろん本当のセキュリティを提供しているわけではありません。 |
| 136 | + |
| 137 | +## 重複の削減 |
| 138 | + |
| 139 | +コードの重複を減らすことは、**FastAPI**の中核的なアイデアの1つです。 |
| 140 | + |
| 141 | +コードの重複が増えると、バグやセキュリティの問題、コードの非同期化問題(ある場所では更新しても他の場所では更新されない場合)などが発生する可能性が高くなります。 |
| 142 | + |
| 143 | +そして、これらのモデルは全てのデータを共有し、属性名や型を重複させています。 |
| 144 | + |
| 145 | +もっと良い方法があります。 |
| 146 | + |
| 147 | +他のモデルのベースとなる`UserBase`モデルを宣言することができます。そして、そのモデルの属性(型宣言、検証など)を継承するサブクラスを作ることができます。 |
| 148 | + |
| 149 | +データの変換、検証、文書化などはすべて通常通りに動作します。 |
| 150 | + |
| 151 | +このようにして、モデル間の違いだけを宣言することができます: |
| 152 | + |
| 153 | +```Python hl_lines="9 15 16 19 20 23 24" |
| 154 | +{!../../../docs_src/extra_models/tutorial002.py!} |
| 155 | +``` |
| 156 | + |
| 157 | +## `Union`または`anyOf` |
| 158 | + |
| 159 | +レスポンスを2つの型の`Union`として宣言することができます。 |
| 160 | + |
| 161 | +OpenAPIでは`anyOf`で定義されます。 |
| 162 | + |
| 163 | +そのためには、標準的なPythonの型ヒント<a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>を使用します: |
| 164 | + |
| 165 | +```Python hl_lines="1 14 15 18 19 20 33" |
| 166 | +{!../../../docs_src/extra_models/tutorial003.py!} |
| 167 | +``` |
| 168 | + |
| 169 | +## モデルのリスト |
| 170 | + |
| 171 | +同じように、オブジェクトのリストのレスポンスを宣言することができます。 |
| 172 | + |
| 173 | +そのためには、標準のPythonの`typing.List`を使用する: |
| 174 | + |
| 175 | +```Python hl_lines="1 20" |
| 176 | +{!../../../docs_src/extra_models/tutorial004.py!} |
| 177 | +``` |
| 178 | + |
| 179 | +## 任意の`dict`を持つレスポンス |
| 180 | + |
| 181 | +また、Pydanticモデルを使用せずに、キーと値の型だけを定義した任意の`dict`を使ってレスポンスを宣言することもできます。 |
| 182 | + |
| 183 | +これは、有効なフィールド・属性名(Pydanticモデルに必要なもの)を事前に知らない場合に便利です。 |
| 184 | + |
| 185 | +この場合、`typing.Dict`を使用することができます: |
| 186 | + |
| 187 | +```Python hl_lines="1 8" |
| 188 | +{!../../../docs_src/extra_models/tutorial005.py!} |
| 189 | +``` |
| 190 | + |
| 191 | +## まとめ |
| 192 | + |
| 193 | +複数のPydanticモデルを使用し、ケースごとに自由に継承します。 |
| 194 | + |
| 195 | +エンティティが異なる「状態」を持たなければならない場合は、エンティティごとに単一のデータモデルを持つ必要はありません。`password` や `password_hash` やパスワードなしなどのいくつかの「状態」をもつユーザー「エンティティ」の場合の様にすれば良いです。 |
0 commit comments