mirror of https://github.com/tiangolo/fastapi.git
Add missing
This commit is contained in:
parent
fd84e7418f
commit
b776c303bd
|
|
@ -0,0 +1,503 @@
|
|||
# LLM テストファイル { #llm-test-file }
|
||||
|
||||
このドキュメントは、ドキュメントを翻訳する <abbr title="Large Language Model - 大規模言語モデル">LLM</abbr> が、`scripts/translate.py` の `general_prompt` と、`docs/{language code}/llm-prompt.md` の言語固有プロンプトを理解しているかをテストします。言語固有プロンプトは `general_prompt` の末尾に追加されます。
|
||||
|
||||
ここに追加したテストは、すべての言語固有プロンプトの設計者が参照します。
|
||||
|
||||
使い方:
|
||||
|
||||
* 言語固有プロンプトを用意します - `docs/{language code}/llm-prompt.md`。
|
||||
* この文書を希望するターゲット言語に新規で翻訳します(例: `translate.py` の `translate-page` コマンド)。これにより `docs/{language code}/docs/_llm-test.md` に翻訳が作成されます。
|
||||
* 翻訳が問題ないか確認します。
|
||||
* 必要であれば、言語固有プロンプト、general プロンプト、または英語ドキュメントを改善します。
|
||||
* その後、翻訳に残っている問題を手動で修正し、良い翻訳にします。
|
||||
* 良い翻訳を用意した状態でもう一度翻訳します。理想的な結果は、LLM が翻訳に一切変更を加えないことです。つまり general プロンプトと言語固有プロンプトが最良であることを意味します(時々いくつかランダムに見える変更を行うことがあります。理由は <a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM は決定論的アルゴリズムではない</a> ためです)。
|
||||
|
||||
テスト内容:
|
||||
|
||||
## コードスニペット { #code-snippets }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
これはコードスニペットです: `foo`。そしてこれもコードスニペットです: `bar`。さらにもう一つ: `baz quux`。
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
コードスニペットの内容はそのままにしておく必要があります。
|
||||
|
||||
`scripts/translate.py` の general プロンプト内「### Content of code snippets」の節を参照してください。
|
||||
|
||||
////
|
||||
|
||||
## 引用 { #quotes }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
昨日、友人はこう書きました。「incorrectly を正しく綴れば、あなたはそれを間違って綴ったことになる」。それに対して私はこう答えました。「そのとおり。ただし『incorrectly』は誤りで、『"incorrectly"』ではありません」。
|
||||
|
||||
/// note | 備考
|
||||
|
||||
LLM はおそらくここを誤って翻訳します。重要なのは、再翻訳時に修正済みの翻訳を維持できるかどうかだけです。
|
||||
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
プロンプト設計者は、ストレートクォートをタイポグラフィックな引用符に変換するかどうかを選べます。そのままでも問題ありません。
|
||||
|
||||
例として `docs/de/llm-prompt.md` の「### Quotes」の節を参照してください。
|
||||
|
||||
////
|
||||
|
||||
## コードスニペット内の引用 { #quotes-in-code-snippets }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
`pip install "foo[bar]"`
|
||||
|
||||
コードスニペット中の文字列リテラルの例: `"this"`, `'that'`.
|
||||
|
||||
難しい文字列リテラルの例: `f"I like {'oranges' if orange else "apples"}"`
|
||||
|
||||
ハードコア: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"`
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
... ただし、コードスニペット内の引用符はそのままにしておく必要があります。
|
||||
|
||||
////
|
||||
|
||||
## コードブロック { #code-blocks }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
Bash のコード例です...
|
||||
|
||||
```bash
|
||||
# 宇宙にあいさつを表示
|
||||
echo "Hello universe"
|
||||
```
|
||||
|
||||
...そしてコンソールのコード例です...
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting server
|
||||
Searching for package file structure
|
||||
```
|
||||
|
||||
...さらに別のコンソールのコード例です...
|
||||
|
||||
```console
|
||||
// ディレクトリ "code" を作成
|
||||
$ mkdir code
|
||||
// そのディレクトリに移動
|
||||
$ cd code
|
||||
```
|
||||
|
||||
...そして Python のコード例です...
|
||||
|
||||
```Python
|
||||
wont_work() # これは動作しません 😱
|
||||
works(foo="bar") # これは動作します 🎉
|
||||
```
|
||||
|
||||
...以上です。
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
コードブロック内のコードは、コメントを除き、変更してはいけません。
|
||||
|
||||
`scripts/translate.py` の general プロンプト内「### Content of code blocks」の節を参照してください。
|
||||
|
||||
////
|
||||
|
||||
## タブと色付きボックス { #tabs-and-colored-boxes }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
/// info | 情報
|
||||
いくつかのテキスト
|
||||
///
|
||||
|
||||
/// note | 備考
|
||||
いくつかのテキスト
|
||||
///
|
||||
|
||||
/// note | 技術詳細
|
||||
いくつかのテキスト
|
||||
///
|
||||
|
||||
/// check | 確認
|
||||
いくつかのテキスト
|
||||
///
|
||||
|
||||
/// tip | 豆知識
|
||||
いくつかのテキスト
|
||||
///
|
||||
|
||||
/// warning | 注意
|
||||
いくつかのテキスト
|
||||
///
|
||||
|
||||
/// danger | 警告
|
||||
いくつかのテキスト
|
||||
///
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
タブおよび `Info`/`Note`/`Warning` などのブロックには、タイトルの翻訳を縦棒(`|`)の後ろに追加します。
|
||||
|
||||
`scripts/translate.py` の general プロンプト内「### Special blocks」と「### Tab blocks」の節を参照してください。
|
||||
|
||||
////
|
||||
|
||||
## Web リンクと内部リンク { #web-and-internal-links }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
リンクのテキストは翻訳し、リンク先のアドレスは変更しないでください:
|
||||
|
||||
* [上の見出しへのリンク](#code-snippets)
|
||||
* [内部リンク](index.md#installation){.internal-link target=_blank}
|
||||
* <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">外部リンク</a>
|
||||
* <a href="https://fastapi.tiangolo.com/css/styles.css" class="external-link" target="_blank">スタイルへのリンク</a>
|
||||
* <a href="https://fastapi.tiangolo.com/js/logic.js" class="external-link" target="_blank">スクリプトへのリンク</a>
|
||||
* <a href="https://fastapi.tiangolo.com/img/foo.jpg" class="external-link" target="_blank">画像へのリンク</a>
|
||||
|
||||
リンクのテキストは翻訳し、リンク先のアドレスは翻訳版を指すようにしてください:
|
||||
|
||||
* <a href="https://fastapi.tiangolo.com/ja/" class="external-link" target="_blank">FastAPI リンク</a>
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
リンクのテキストは翻訳し、アドレスは変更しないでください。例外は、FastAPI ドキュメントのページへの絶対 URL です。その場合は翻訳版へのリンクにします。
|
||||
|
||||
`scripts/translate.py` の general プロンプト内「### Links」の節を参照してください。
|
||||
|
||||
////
|
||||
|
||||
## HTML "abbr" 要素 { #html-abbr-elements }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
ここでは HTML の "abbr" 要素で包まれたものをいくつか示します(いくつかは架空です):
|
||||
|
||||
### abbr が完全な語句を示す { #the-abbr-gives-a-full-phrase }
|
||||
|
||||
* <abbr title="Getting Things Done - 仕事を成し遂げること">GTD</abbr>
|
||||
* <abbr title="less than - より小さい"><code>lt</code></abbr>
|
||||
* <abbr title="XML Web Token - XML ウェブトークン">XWT</abbr>
|
||||
* <abbr title="Parallel Server Gateway Interface - 並列サーバーゲートウェイインターフェース">PSGI</abbr>
|
||||
|
||||
### abbr が完全な語句と説明を示す { #the-abbr-gives-a-full-phrase-and-an-explanation }
|
||||
|
||||
* <abbr title="Mozilla Developer Network - Mozilla 開発者ネットワーク: 開発者向けドキュメント、Firefox の開発元が執筆">MDN</abbr>
|
||||
* <abbr title="Input/Output - 入出力: ディスクの読み書き、ネットワーク通信。">I/O</abbr>.
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
"abbr" 要素の "title" 属性は特定の指示に従って翻訳します。
|
||||
|
||||
翻訳は、英語の語を説明するために独自の "abbr" 要素を追加してもよく、LLM はそれらを削除してはいけません。
|
||||
|
||||
`scripts/translate.py` の general プロンプト内「### HTML abbr elements」の節を参照してください。
|
||||
|
||||
////
|
||||
|
||||
## HTML "dfn" 要素 { #html-dfn-elements }
|
||||
|
||||
* <dfn title="ある方法で接続・連携して動作するよう構成された複数のマシンの集合">クラスター</dfn>
|
||||
* <dfn title="入力層と出力層の間に多数の隠れ層を持つ人工ニューラルネットワークを用いる機械学習の手法で、その内部構造を包括的に形成する">ディープラーニング</dfn>
|
||||
|
||||
## 見出し { #headings }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
### Web アプリを開発する - チュートリアル { #develop-a-webapp-a-tutorial }
|
||||
|
||||
こんにちは。
|
||||
|
||||
### 型ヒントとアノテーション { #type-hints-and-annotations }
|
||||
|
||||
またこんにちは。
|
||||
|
||||
### スーパークラスとサブクラス { #super-and-subclasses }
|
||||
|
||||
またこんにちは。
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
見出しに関する唯一の厳格なルールは、リンクが壊れないように、LLM が中括弧内のハッシュ部分を変更しないことです。
|
||||
|
||||
`scripts/translate.py` の general プロンプト内「### Headings」の節を参照してください。
|
||||
|
||||
言語固有の指示については、例として `docs/de/llm-prompt.md` の「### Headings」の節を参照してください。
|
||||
|
||||
////
|
||||
|
||||
## ドキュメントで使う用語 { #terms-used-in-the-docs }
|
||||
|
||||
//// tab | テスト
|
||||
|
||||
* you
|
||||
* your
|
||||
|
||||
* e.g.
|
||||
* etc.
|
||||
|
||||
* `foo` を `int` として
|
||||
* `bar` を `str` として
|
||||
* `baz` を `list` として
|
||||
|
||||
* チュートリアル - ユーザーガイド
|
||||
* 上級ユーザーガイド
|
||||
* SQLModel ドキュメント
|
||||
* API ドキュメント
|
||||
* 自動生成ドキュメント
|
||||
|
||||
* データサイエンス
|
||||
* ディープラーニング
|
||||
* 機械学習
|
||||
* 依存性注入
|
||||
* HTTP Basic 認証
|
||||
* HTTP Digest
|
||||
* ISO 形式
|
||||
* JSON Schema 規格
|
||||
* JSON スキーマ
|
||||
* スキーマ定義
|
||||
* Password Flow
|
||||
* モバイル
|
||||
|
||||
* 非推奨
|
||||
* 設計された
|
||||
* 無効
|
||||
* オンザフライ
|
||||
* 標準
|
||||
* デフォルト
|
||||
* 大文字小文字を区別
|
||||
* 大文字小文字を区別しない
|
||||
|
||||
* アプリケーションを提供する
|
||||
* ページを配信する
|
||||
|
||||
* アプリ
|
||||
* アプリケーション
|
||||
|
||||
* リクエスト
|
||||
* レスポンス
|
||||
* エラーレスポンス
|
||||
|
||||
* path operation
|
||||
* path operation デコレータ
|
||||
* path operation 関数
|
||||
|
||||
* ボディ
|
||||
* リクエストボディ
|
||||
* レスポンスボディ
|
||||
* JSON ボディ
|
||||
* フォームボディ
|
||||
* ファイルボディ
|
||||
* 関数本体
|
||||
|
||||
* パラメータ
|
||||
* ボディパラメータ
|
||||
* パスパラメータ
|
||||
* クエリパラメータ
|
||||
* Cookie パラメータ
|
||||
* ヘッダーパラメータ
|
||||
* フォームパラメータ
|
||||
* 関数パラメータ
|
||||
|
||||
* イベント
|
||||
* 起動イベント
|
||||
* サーバーの起動
|
||||
* シャットダウンイベント
|
||||
* lifespan イベント
|
||||
|
||||
* ハンドラ
|
||||
* イベントハンドラ
|
||||
* 例外ハンドラ
|
||||
* 処理する
|
||||
|
||||
* モデル
|
||||
* Pydantic モデル
|
||||
* データモデル
|
||||
* データベースモデル
|
||||
* フォームモデル
|
||||
* モデルオブジェクト
|
||||
|
||||
* クラス
|
||||
* 基底クラス
|
||||
* 親クラス
|
||||
* サブクラス
|
||||
* 子クラス
|
||||
* 兄弟クラス
|
||||
* クラスメソッド
|
||||
|
||||
* ヘッダー
|
||||
* ヘッダー(複数)
|
||||
* 認可ヘッダー
|
||||
* `Authorization` ヘッダー
|
||||
* Forwarded ヘッダー
|
||||
|
||||
* 依存性注入システム
|
||||
* 依存関係
|
||||
* dependable
|
||||
* dependant
|
||||
|
||||
* I/O バウンド
|
||||
* CPU バウンド
|
||||
* 同時実行性
|
||||
* 並列性
|
||||
* マルチプロセッシング
|
||||
|
||||
* env var
|
||||
* 環境変数
|
||||
* `PATH`
|
||||
* `PATH` 環境変数
|
||||
|
||||
* 認証
|
||||
* 認証プロバイダ
|
||||
* 認可
|
||||
* 認可フォーム
|
||||
* 認可プロバイダ
|
||||
* ユーザーが認証する
|
||||
* システムがユーザーを認証する
|
||||
|
||||
* CLI
|
||||
* コマンドラインインターフェース
|
||||
|
||||
* サーバー
|
||||
* クライアント
|
||||
|
||||
* クラウドプロバイダ
|
||||
* クラウドサービス
|
||||
|
||||
* 開発
|
||||
* 開発段階
|
||||
|
||||
* dict
|
||||
* 辞書
|
||||
* 列挙型
|
||||
* Enum
|
||||
* 列挙メンバー
|
||||
|
||||
* エンコーダー
|
||||
* デコーダー
|
||||
* エンコードする
|
||||
* デコードする
|
||||
|
||||
* 例外
|
||||
* 送出する
|
||||
|
||||
* 式
|
||||
* 文
|
||||
|
||||
* フロントエンド
|
||||
* バックエンド
|
||||
|
||||
* GitHub ディスカッション
|
||||
* GitHub Issue
|
||||
|
||||
* パフォーマンス
|
||||
* パフォーマンス最適化
|
||||
|
||||
* 戻り値の型
|
||||
* 戻り値
|
||||
|
||||
* セキュリティ
|
||||
* セキュリティスキーム
|
||||
|
||||
* タスク
|
||||
* バックグラウンドタスク
|
||||
* タスク関数
|
||||
|
||||
* テンプレート
|
||||
* テンプレートエンジン
|
||||
|
||||
* 型アノテーション
|
||||
* 型ヒント
|
||||
|
||||
* サーバーワーカー
|
||||
* Uvicorn ワーカー
|
||||
* Gunicorn ワーカー
|
||||
* ワーカープロセス
|
||||
* ワーカークラス
|
||||
* ワークロード
|
||||
|
||||
* デプロイ
|
||||
* デプロイする
|
||||
|
||||
* SDK
|
||||
* ソフトウェア開発キット
|
||||
|
||||
* `APIRouter`
|
||||
* `requirements.txt`
|
||||
* Bearer Token
|
||||
* 破壊的変更
|
||||
* バグ
|
||||
* ボタン
|
||||
* 呼び出し可能
|
||||
* コード
|
||||
* コミット
|
||||
* コンテキストマネージャ
|
||||
* コルーチン
|
||||
* データベースセッション
|
||||
* ディスク
|
||||
* ドメイン
|
||||
* エンジン
|
||||
* フェイクの X
|
||||
* HTTP GET メソッド
|
||||
* アイテム
|
||||
* ライブラリ
|
||||
* ライフスパン
|
||||
* ロック
|
||||
* ミドルウェア
|
||||
* モバイルアプリケーション
|
||||
* モジュール
|
||||
* マウント
|
||||
* ネットワーク
|
||||
* オリジン
|
||||
* オーバーライド
|
||||
* ペイロード
|
||||
* プロセッサ
|
||||
* プロパティ
|
||||
* プロキシ
|
||||
* プルリクエスト
|
||||
* クエリ
|
||||
* RAM
|
||||
* リモートマシン
|
||||
* ステータスコード
|
||||
* 文字列
|
||||
* タグ
|
||||
* Web フレームワーク
|
||||
* ワイルドカード
|
||||
* 返す
|
||||
* 検証する
|
||||
|
||||
////
|
||||
|
||||
//// tab | 情報
|
||||
|
||||
これはドキュメントで見られる(主に)技術用語の不完全かつ規範的でない一覧です。プロンプト設計者が、LLM がどの用語で手助けを必要としているかを把握するのに役立つかもしれません。例えば、良い翻訳を最適でない翻訳に戻してしまう場合や、あなたの言語での活用・格変化に問題がある場合などです。
|
||||
|
||||
`docs/de/llm-prompt.md` の「### List of English terms and their preferred German translations」の節を参照してください。
|
||||
|
||||
////
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# 概要 { #about }
|
||||
|
||||
FastAPI の概要、その設計やインスピレーションなどについて解説します。🤓
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
# OpenAPI の追加レスポンス { #additional-responses-in-openapi }
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
これは比較的高度なトピックです。
|
||||
|
||||
FastAPI を使い始めたばかりであれば、これは不要かもしれません。
|
||||
|
||||
///
|
||||
|
||||
追加のステータスコード、メディアタイプ、説明などを伴う追加レスポンスを宣言できます。
|
||||
|
||||
それらの追加レスポンスは OpenAPI スキーマに含まれ、API ドキュメントにも表示されます。
|
||||
|
||||
ただし、それらの追加レスポンスについては、ステータスコードとコンテンツを指定して `JSONResponse` などの `Response` を直接返す必要があります。
|
||||
|
||||
## `model` を使った追加レスポンス { #additional-response-with-model }
|
||||
|
||||
*path operation デコレータ*に `responses` パラメータを渡せます。
|
||||
|
||||
これは `dict` を受け取り、キーは各レスポンスのステータスコード(例: `200`)、値は各レスポンスの情報を含む別の `dict` です。
|
||||
|
||||
それぞれのレスポンス `dict` には、`response_model` と同様に Pydantic モデルを格納する `model` キーを含められます。
|
||||
|
||||
FastAPI はそのモデルから JSON Schema を生成し、OpenAPI の適切な場所に含めます。
|
||||
|
||||
例えば、ステータスコード `404` と Pydantic モデル `Message` を持つ別のレスポンスを宣言するには、次のように書けます:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *}
|
||||
|
||||
/// note | 備考
|
||||
|
||||
`JSONResponse` を直接返す必要がある点に注意してください。
|
||||
|
||||
///
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`model` キーは OpenAPI の一部ではありません。
|
||||
|
||||
FastAPI はそこから Pydantic モデルを取得して JSON Schema を生成し、適切な場所に配置します。
|
||||
|
||||
適切な場所は次のとおりです:
|
||||
|
||||
- `content` キーの中。これは値として別の JSON オブジェクト(`dict`)を持ち、その中に次が含まれます:
|
||||
- メディアタイプ(例: `application/json`)をキーとし、値としてさらに別の JSON オブジェクトを持ち、その中に次が含まれます:
|
||||
- `schema` キー。値としてモデル由来の JSON Schema を持ち、ここが正しい配置場所です。
|
||||
- FastAPI はここに、スキーマを直接埋め込む代わりに OpenAPI 内のグローバルな JSON Schema への参照を追加します。これにより、他のアプリケーションやクライアントがそれらの JSON Schema を直接利用し、より良いコード生成ツール等を提供できます。
|
||||
|
||||
///
|
||||
|
||||
この *path operation* のために OpenAPI に生成されるレスポンスは次のとおりです:
|
||||
|
||||
```JSON hl_lines="3-12"
|
||||
{
|
||||
"responses": {
|
||||
"404": {
|
||||
"description": "Additional Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Item"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
スキーマは OpenAPI スキーマ内の別の場所への参照になります:
|
||||
|
||||
```JSON hl_lines="4-16"
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Message": {
|
||||
"title": "Message",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"title": "Message",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"required": [
|
||||
"id",
|
||||
"value"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"title": "Value",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"msg": {
|
||||
"title": "Message",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"title": "Error Type",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## メインのレスポンスに追加のメディアタイプ { #additional-media-types-for-the-main-response }
|
||||
|
||||
同じ `responses` パラメータを使って、同一のメインレスポンスに別のメディアタイプを追加できます。
|
||||
|
||||
例えば、`image/png` の追加メディアタイプを加え、あなたの *path operation* が JSON オブジェクト(メディアタイプ `application/json`)または PNG 画像を返せることを宣言できます:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
|
||||
|
||||
/// note | 備考
|
||||
|
||||
画像は `FileResponse` を使って直接返す必要がある点に注意してください。
|
||||
|
||||
///
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`responses` パラメータで明示的に別のメディアタイプを指定しない限り、FastAPI はレスポンスがメインのレスポンスクラスと同じメディアタイプ(デフォルトは `application/json`)であるとみなします。
|
||||
|
||||
ただし、メディアタイプが `None` のカスタムレスポンスクラスを指定している場合、モデルが関連付けられた追加レスポンスには FastAPI は `application/json` を使用します。
|
||||
|
||||
///
|
||||
|
||||
## 情報の結合 { #combining-information }
|
||||
|
||||
`response_model`、`status_code`、`responses` パラメータなど、複数の場所からのレスポンス情報を組み合わせることもできます。
|
||||
|
||||
`response_model` を宣言し、デフォルトのステータスコード `200`(必要なら任意のコード)を使い、その同じレスポンスに対する追加情報を `responses` で OpenAPI スキーマに直接記述できます。
|
||||
|
||||
FastAPI は `responses` にある追加情報を保持し、モデルの JSON Schema と結合します。
|
||||
|
||||
例えば、Pydantic モデルを用い、独自の `description` を持つステータスコード `404` のレスポンスを宣言できます。
|
||||
|
||||
さらに、`response_model` を使うステータスコード `200` のレスポンスに独自の `example` を含めることもできます:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *}
|
||||
|
||||
これらはすべて結合されて OpenAPI に含まれ、API ドキュメントに表示されます:
|
||||
|
||||
<img src="/img/tutorial/additional-responses/image01.png">
|
||||
|
||||
## 事前定義レスポンスとカスタムの組み合わせ { #combine-predefined-responses-and-custom-ones }
|
||||
|
||||
多くの *path operations* に適用できる事前定義のレスポンスを用意しつつ、各 *path operation* ごとに必要なカスタムレスポンスと組み合わせたい場合があります。
|
||||
|
||||
そのような場合、Python の `**dict_to_unpack` による `dict` の「アンパック」テクニックを使えます:
|
||||
|
||||
```Python
|
||||
old_dict = {
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
}
|
||||
new_dict = {**old_dict, "new key": "new value"}
|
||||
```
|
||||
|
||||
ここでは、`new_dict` には `old_dict` のすべてのキーと値に加え、新しいキーと値が含まれます:
|
||||
|
||||
```Python
|
||||
{
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
"new key": "new value",
|
||||
}
|
||||
```
|
||||
|
||||
このテクニックを使うと、*path operations* で事前定義レスポンスを再利用し、さらにカスタムのレスポンスを組み合わせられます。
|
||||
|
||||
例えば:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
|
||||
|
||||
## OpenAPI レスポンスの詳細 { #more-information-about-openapi-responses }
|
||||
|
||||
レスポンスに正確に何を含められるかは、OpenAPI 仕様の次のセクションを参照してください:
|
||||
|
||||
- <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">OpenAPI の Responses Object</a>。ここには `Response Object` が含まれます。
|
||||
- <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">OpenAPI の Response Object</a>。`responses` パラメータ内の各レスポンスに、ここで定義されている要素を直接含められます。`description`、`headers`、`content`(ここで異なるメディアタイプや JSON Schema を宣言します)、`links` など。
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
# 高度な依存関係 { #advanced-dependencies }
|
||||
|
||||
## パラメータ化された依存関係 { #parameterized-dependencies }
|
||||
|
||||
これまで見てきた依存関係は、固定の関数またはクラスでした。
|
||||
|
||||
しかし、多くの異なる関数やクラスを宣言せずに、その依存関係にパラメータを設定したい場合があります。
|
||||
|
||||
たとえば、クエリパラメータ `q` に、ある固定の内容が含まれているかを検査する依存関係が欲しいとします。
|
||||
|
||||
ただし、その固定の内容はパラメータ化できるようにしたいです。
|
||||
|
||||
## "callable" なインスタンス { #a-callable-instance }
|
||||
|
||||
Python には、クラスのインスタンスを "callable" にする方法があります。
|
||||
|
||||
クラス自体(これはすでに callable です)ではなく、そのクラスのインスタンスです。
|
||||
|
||||
そのためには、`__call__` メソッドを宣言します:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *}
|
||||
|
||||
この場合、この `__call__` が、**FastAPI** が追加のパラメータやサブ依存関係を確認するために使うものになり、後であなたの *path operation 関数* のパラメータに値を渡すために呼び出されるものになります。
|
||||
|
||||
## インスタンスのパラメータ化 { #parameterize-the-instance }
|
||||
|
||||
そして、`__init__` を使って、依存関係を「パラメータ化」するために利用できるインスタンスのパラメータを宣言できます:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *}
|
||||
|
||||
この場合、**FastAPI** は `__init__` に触れたり気にかけたりすることはありません。私たちがコード内で直接使います。
|
||||
|
||||
## インスタンスの作成 { #create-an-instance }
|
||||
|
||||
このクラスのインスタンスは次のように作成できます:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *}
|
||||
|
||||
このようにして依存関係を「パラメータ化」できます。いまや `"bar"` が属性 `checker.fixed_content` として中に保持されています。
|
||||
|
||||
## インスタンスを依存関係として使う { #use-the-instance-as-a-dependency }
|
||||
|
||||
その後、`Depends(FixedContentQueryChecker)` の代わりに `Depends(checker)` でこの `checker` を使えます。依存関係はクラスそのものではなく、インスタンスである `checker` だからです。
|
||||
|
||||
依存関係を解決するとき、**FastAPI** はこの `checker` を次のように呼び出します:
|
||||
|
||||
```Python
|
||||
checker(q="somequery")
|
||||
```
|
||||
|
||||
...そして、その戻り値を *path operation 関数* 内の依存関係の値として、パラメータ `fixed_content_included` に渡します:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
ここまでの内容は回りくどく感じられるかもしれません。まだどのように役立つかが明確でないかもしれません。
|
||||
|
||||
これらの例は意図的に単純ですが、仕組みを示しています。
|
||||
|
||||
セキュリティの章では、同じやり方で実装されたユーティリティ関数があります。
|
||||
|
||||
ここまでを理解できていれば、そうしたセキュリティ用ユーティリティが内部でどのように動いているかも理解できています。
|
||||
|
||||
///
|
||||
|
||||
## `yield`、`HTTPException`、`except` とバックグラウンドタスクを伴う依存関係 { #dependencies-with-yield-httpexception-except-and-background-tasks }
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
これらの技術的詳細は、ほとんどの場合は不要です。
|
||||
|
||||
主に、0.121.0 より前の FastAPI アプリケーションがあり、`yield` を使う依存関係で問題が発生している場合に有用です。
|
||||
|
||||
///
|
||||
|
||||
`yield` を使う依存関係は、さまざまなユースケースに対応し、いくつかの問題を修正するために時間とともに進化してきました。ここでは変更点の概要を説明します。
|
||||
|
||||
### `yield` と `scope` を伴う依存関係 { #dependencies-with-yield-and-scope }
|
||||
|
||||
バージョン 0.121.0 で、`yield` を使う依存関係に対して `Depends(scope="function")` がサポートされました。
|
||||
|
||||
`Depends(scope="function")` を使うと、`yield` の後の終了コードは、クライアントへレスポンスが返される前、*path operation 関数* が終了した直後に実行されます。
|
||||
|
||||
そして、`Depends(scope="request")`(デフォルト)を使う場合、`yield` の後の終了コードはレスポンス送信後に実行されます。
|
||||
|
||||
詳しくはドキュメント「[`yield` を使う依存関係 - 早期終了と `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope)」を参照してください。
|
||||
|
||||
### `yield` と `StreamingResponse` を伴う依存関係、技術詳細 { #dependencies-with-yield-and-streamingresponse-technical-details }
|
||||
|
||||
FastAPI 0.118.0 より前では、`yield` を使う依存関係を使用すると、*path operation 関数* が戻ってからレスポンス送信直前に終了コードが実行されていました。
|
||||
|
||||
これは、レスポンスがネットワーク上を移動するのを待っている間に、不要にリソースを保持しないようにする意図でした。
|
||||
|
||||
この変更により、`StreamingResponse` を返す場合、`yield` を持つ依存関係の終了コードはすでに実行されていることになりました。
|
||||
|
||||
たとえば、`yield` を持つ依存関係の中でデータベースセッションを持っていた場合、`StreamingResponse` はデータをストリーミングしている間にそのセッションを使えません。というのも、`yield` の後の終了コードでそのセッションがすでにクローズされているからです。
|
||||
|
||||
この挙動は 0.118.0 で元に戻され、`yield` の後の終了コードはレスポンス送信後に実行されるようになりました。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
以下で見るように、これはバージョン 0.106.0 より前の挙動ととても似ていますが、いくつかのコーナーケースに対する改良とバグ修正が含まれています。
|
||||
|
||||
///
|
||||
|
||||
#### 早期終了コードのユースケース { #use-cases-with-early-exit-code }
|
||||
|
||||
特定の条件では、レスポンス送信前に `yield` を持つ依存関係の終了コードを実行する、古い挙動の恩恵を受けられるユースケースがあります。
|
||||
|
||||
例えば、`yield` を持つ依存関係でデータベースセッションを使ってユーザ検証だけを行い、その後は *path operation 関数* 内ではそのデータベースセッションを一切使わない、かつレスポンス送信に長い時間がかかる(例えばデータをゆっくり送る `StreamingResponse`)が、何らかの理由でデータベースは使わない、というケースです。
|
||||
|
||||
この場合、レスポンスの送信が終わるまでデータベースセッションが保持されますが、使わないのであれば保持する必要はありません。
|
||||
|
||||
次のようになります:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
|
||||
|
||||
終了コード、すなわち `Session` の自動クローズは:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
|
||||
|
||||
...の部分で定義されており、遅いデータ送信が終わった後に実行されます:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
|
||||
|
||||
しかし、`generate_stream()` はデータベースセッションを使わないため、レスポンス送信中にセッションを開いたままにしておく必要は実際にはありません。
|
||||
|
||||
SQLModel(または SQLAlchemy)でこの特定のユースケースがある場合は、不要になった時点でセッションを明示的にクローズできます:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
|
||||
|
||||
このようにすると、セッションはデータベース接続を解放するため、他のリクエストがそれを使えるようになります。
|
||||
|
||||
`yield` を持つ依存関係で早期終了が必要な別のユースケースがある場合は、あなたの具体的なユースケースと、なぜ `yield` を持つ依存関係の早期クローズが有益かを説明して、<a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussion の質問</a>を作成してください。
|
||||
|
||||
`yield` を持つ依存関係の早期クローズに納得できるユースケースがある場合は、早期クローズにオプトインする新しい方法を追加することを検討します。
|
||||
|
||||
### `yield` と `except` を伴う依存関係、技術詳細 { #dependencies-with-yield-and-except-technical-details }
|
||||
|
||||
FastAPI 0.110.0 より前では、`yield` を持つ依存関係を使い、その依存関係内で `except` によって例外を捕捉し、再度その例外を送出しなかった場合でも、その例外は自動的に送出(フォワード)され、任意の例外ハンドラまたは内部サーバエラーハンドラに渡されていました。
|
||||
|
||||
これは、ハンドラのないフォワードされた例外(内部サーバエラー)による未処理のメモリ消費を修正し、通常の Python コードの挙動と一貫性を持たせるため、バージョン 0.110.0 で変更されました。
|
||||
|
||||
### バックグラウンドタスクと `yield` を伴う依存関係、技術詳細 { #background-tasks-and-dependencies-with-yield-technical-details }
|
||||
|
||||
FastAPI 0.106.0 より前では、`yield` の後で例外を送出することはできませんでした。`yield` を持つ依存関係の終了コードはレスポンス送信「後」に実行されるため、[例外ハンドラ](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} はすでに実行済みでした。
|
||||
|
||||
これは主に、依存関係が "yield" した同じオブジェクトをバックグラウンドタスク内で利用できるようにするための設計でした。終了コードはバックグラウンドタスク完了後に実行されるからです。
|
||||
|
||||
これは、レスポンスがネットワーク上を移動するのを待っている間にリソースを保持しないようにする意図で、FastAPI 0.106.0 で変更されました。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
加えて、バックグラウンドタスクは通常、独立したロジックの集合であり、(例えば専用のデータベース接続など)それ自身のリソースで個別に扱うべきです。
|
||||
|
||||
そのため、このやり方の方がコードはおそらくよりクリーンになります。
|
||||
|
||||
///
|
||||
|
||||
この挙動に依存していた場合は、バックグラウンドタスク用のリソースをバックグラウンドタスク内部で作成し、`yield` を持つ依存関係のリソースに依存しないデータだけを内部で使用するようにしてください。
|
||||
|
||||
例えば、同じデータベースセッションを使うのではなく、バックグラウンドタスク内で新しいデータベースセッションを作成し、この新しいセッションでデータベースからオブジェクトを取得します。そして、バックグラウンドタスク関数の引数としてデータベースのオブジェクト自体を渡すのではなく、そのオブジェクトの ID を渡し、バックグラウンドタスク関数内でもう一度そのオブジェクトを取得します。
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# 高度な Python の型 { #advanced-python-types }
|
||||
|
||||
Python の型を扱うときに役立つ追加のアイデアをいくつか紹介します。
|
||||
|
||||
## `Union` または `Optional` の利用 { #using-union-or-optional }
|
||||
|
||||
何らかの理由で `|` が使えない場合、たとえば型アノテーションではなく `response_model=` のような場所では、縦棒(`|`)の代わりに `typing` の `Union` を使えます。
|
||||
|
||||
例えば、`str` または `None` になり得ることを宣言できます:
|
||||
|
||||
```python
|
||||
from typing import Union
|
||||
|
||||
|
||||
def say_hi(name: Union[str, None]):
|
||||
print(f"Hi {name}!")
|
||||
```
|
||||
|
||||
`typing` には、`None` を取り得ることを宣言するための短縮形として `Optional` もあります。
|
||||
|
||||
ここからは私のとても主観的な提案です:
|
||||
|
||||
- 🚨 `Optional[SomeType]` の使用は避けましょう
|
||||
- 代わりに ✨ **`Union[SomeType, None]` を使いましょう** ✨。
|
||||
|
||||
どちらも等価で内部的には同一ですが、「optional(任意)」という語が値が任意だと誤解させやすく、実際の意味は「`None` を取り得る」であり、任意ではなく依然として必須である場合でもそうです。そのため `Optional` より `Union` を勧めます。
|
||||
|
||||
`Union[SomeType, None]` の方が意味がより明確だと思います。
|
||||
|
||||
これは用語や名前付けの話に過ぎませんが、その言葉があなたやチームメイトのコードの捉え方に影響します。
|
||||
|
||||
例として次の関数を見てみましょう:
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def say_hi(name: Optional[str]):
|
||||
print(f"Hey {name}!")
|
||||
```
|
||||
|
||||
パラメータ `name` は `Optional[str]` と定義されていますが、任意ではありません。このパラメータなしで関数を呼び出すことはできません:
|
||||
|
||||
```Python
|
||||
say_hi() # あっ、これはエラーになります!😱
|
||||
```
|
||||
|
||||
`name` パラメータにはデフォルト値がないため、依然として必須(任意ではない)です。ただし、`name` は値として `None` を受け付けます:
|
||||
|
||||
```Python
|
||||
say_hi(name=None) # これは動作します。None は有効です 🎉
|
||||
```
|
||||
|
||||
朗報として、多くの場合は単純に `|` を使って型の Union を定義できます:
|
||||
|
||||
```python
|
||||
def say_hi(name: str | None):
|
||||
print(f"Hey {name}!")
|
||||
```
|
||||
|
||||
したがって、通常は `Optional` や `Union` といった名前を気にする必要はありません。😎
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
# 非同期テスト { #async-tests }
|
||||
|
||||
これまでに、提供されている `TestClient` を使って **FastAPI** アプリケーションをテストする方法を見てきました。ここまでは、`async` 関数を使わない同期テストのみでした。
|
||||
|
||||
テストで非同期関数を使えると、たとえばデータベースへ非同期にクエリする場合などに便利です。非同期データベースライブラリを使いながら、FastAPI アプリにリクエストを送り、その後バックエンドが正しいデータをデータベースに書き込めたかを検証したい、といったケースを想像してください。
|
||||
|
||||
その方法を見ていきます。
|
||||
|
||||
## pytest.mark.anyio { #pytest-mark-anyio }
|
||||
|
||||
テスト内で非同期関数を呼び出したい場合、テスト関数自体も非同期である必要があります。AnyIO はこれを実現するための便利なプラグインを提供しており、特定のテスト関数を非同期で呼び出すことを指定できます。
|
||||
|
||||
## HTTPX { #httpx }
|
||||
|
||||
**FastAPI** アプリケーションが通常の `def` 関数を使っていても、その内側は依然として `async` アプリケーションです。
|
||||
|
||||
`TestClient` は、標準の pytest を使って通常の `def` のテスト関数から非同期の FastAPI アプリを呼び出すための「おまじない」を内部で行います。しかし、その「おまじない」はテスト関数自体が非同期の場合には機能しません。テストを非同期で実行すると、テスト関数内で `TestClient` は使えなくなります。
|
||||
|
||||
`TestClient` は <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> を基に作られており、幸いなことに API のテストには HTTPX を直接利用できます。
|
||||
|
||||
## 例 { #example }
|
||||
|
||||
簡単な例として、[大きなアプリケーション](../tutorial/bigger-applications.md){.internal-link target=_blank} と [テスト](../tutorial/testing.md){.internal-link target=_blank} で説明したものに似たファイル構成を考えます:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── test_main.py
|
||||
```
|
||||
|
||||
`main.py` は次のようになります:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py310/main.py *}
|
||||
|
||||
`test_main.py` は `main.py` のテストを持ち、次のようになります:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py *}
|
||||
|
||||
## 実行 { #run-it }
|
||||
|
||||
テストはいつも通り次で実行できます:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pytest
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 詳細 { #in-detail }
|
||||
|
||||
マーカー `@pytest.mark.anyio` は、このテスト関数を非同期で呼び出すべきであることを pytest に伝えます:
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`TestClient` を使っていたときと異なり、テスト関数は `def` ではなく `async def` になっている点に注意してください。
|
||||
|
||||
///
|
||||
|
||||
次に、アプリを渡して `AsyncClient` を作成し、`await` を使って非同期リクエストを送信できます。
|
||||
|
||||
{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *}
|
||||
|
||||
これは次と同等です:
|
||||
|
||||
```Python
|
||||
response = client.get('/')
|
||||
```
|
||||
|
||||
...これまでは `TestClient` でリクエストを送っていました。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
新しい `AsyncClient` では async/await を使っている点に注意してください。リクエストは非同期です。
|
||||
|
||||
///
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
アプリケーションが lifespan イベントに依存している場合、`AsyncClient` はそれらのイベントをトリガーしません。確実にトリガーするには、<a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a> の `LifespanManager` を使用してください。
|
||||
|
||||
///
|
||||
|
||||
## その他の非同期関数呼び出し { #other-asynchronous-function-calls }
|
||||
|
||||
テスト関数が非同期になったので、FastAPI アプリへのリクエスト送信以外の `async` 関数も、コードの他の場所と同様に呼び出して(`await` して)使えます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
テストに非同期関数呼び出しを統合した際に(例: <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB の MotorClient</a> 使用時)、`RuntimeError: Task attached to a different loop` に遭遇した場合は、イベントループを必要とするオブジェクトは非同期関数内でのみインスタンス化するようにしてください。例えば `@app.on_event("startup")` コールバック内で行います。
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,466 @@
|
|||
# プロキシの背後 { #behind-a-proxy }
|
||||
|
||||
多くの状況で、FastAPI アプリの前段に **Traefik** や **Nginx** のような**プロキシ**を置きます。
|
||||
|
||||
これらのプロキシは HTTPS 証明書などの処理を担います。
|
||||
|
||||
## プロキシの転送ヘッダー { #proxy-forwarded-headers }
|
||||
|
||||
アプリケーションの前段にある **プロキシ** は通常、リクエストを **サーバー** に送る前に、そのリクエストがプロキシによって転送されたことを知らせるためのヘッダーを動的に付与し、使用中の元の(公開)URL(ドメインを含む)や HTTPS 使用などの情報を伝えます。
|
||||
|
||||
**サーバー** プログラム(例えば **FastAPI CLI** 経由の **Uvicorn**)はこれらのヘッダーを解釈し、その情報をアプリケーションに渡すことができます。
|
||||
|
||||
しかしセキュリティ上、サーバーは自分が信頼できるプロキシの背後にあると分からないため、これらのヘッダーを解釈しません。
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
プロキシのヘッダーは次のとおりです:
|
||||
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
|
||||
|
||||
///
|
||||
|
||||
### プロキシ転送ヘッダーを有効化 { #enable-proxy-forwarded-headers }
|
||||
|
||||
FastAPI CLI を *CLI オプション* `--forwarded-allow-ips` 付きで起動し、転送ヘッダーを信頼して読んでよい IP アドレスを指定できます。
|
||||
|
||||
`--forwarded-allow-ips="*"` とすると、すべての送信元 IP を信頼します。
|
||||
|
||||
**サーバー** が信頼できる **プロキシ** の背後にあり、そのプロキシからのみ接続される場合、プロキシの IP を受け入れるようになります。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run --forwarded-allow-ips="*"
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### HTTPS を伴うリダイレクト { #redirects-with-https }
|
||||
|
||||
例えば、*path operation* `/items/` を定義しているとします:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *}
|
||||
|
||||
クライアントが `/items` にアクセスすると、既定では `/items/` にリダイレクトされます。
|
||||
|
||||
しかし、*CLI オプション* `--forwarded-allow-ips` を設定する前は、`http://localhost:8000/items/` にリダイレクトされる場合があります。
|
||||
|
||||
ですが、アプリケーションは `https://mysuperapp.com` で公開されており、`https://mysuperapp.com/items/` にリダイレクトされるべきかもしれません。
|
||||
|
||||
`--proxy-headers` を設定すると、FastAPI は正しい場所にリダイレクトできるようになります。😎
|
||||
|
||||
```
|
||||
https://mysuperapp.com/items/
|
||||
```
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
HTTPS について詳しく知りたい場合は、[HTTPS について](../deployment/https.md){.internal-link target=_blank} を参照してください。
|
||||
|
||||
///
|
||||
|
||||
### プロキシ転送ヘッダーの仕組み { #how-proxy-forwarded-headers-work }
|
||||
|
||||
クライアントと **アプリケーションサーバー** の間で、**プロキシ** がどのように転送ヘッダーを追加するかを図示します:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Proxy as Proxy/Load Balancer
|
||||
participant Server as FastAPI Server
|
||||
|
||||
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items
|
||||
|
||||
Note over Proxy: Proxy adds forwarded headers
|
||||
|
||||
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
|
||||
|
||||
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set)
|
||||
|
||||
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs
|
||||
|
||||
Proxy->>Client: HTTPS Response
|
||||
```
|
||||
|
||||
**プロキシ** は元のクライアントリクエストを受け取り、**アプリケーションサーバー** に渡す前に特別な「転送」ヘッダー(`X-Forwarded-*`)を追加します。
|
||||
|
||||
これらのヘッダーは、通常は失われる元のリクエストの情報を保持します:
|
||||
|
||||
* **X-Forwarded-For**: 元のクライアントの IP アドレス
|
||||
* **X-Forwarded-Proto**: 元のプロトコル(`https`)
|
||||
* **X-Forwarded-Host**: 元のホスト(`mysuperapp.com`)
|
||||
|
||||
**FastAPI CLI** を `--forwarded-allow-ips` で設定すると、これらのヘッダーを信頼して使用し、たとえばリダイレクトで正しい URL を生成します。
|
||||
|
||||
## パスプレフィックスを削除するプロキシ { #proxy-with-a-stripped-path-prefix }
|
||||
|
||||
アプリケーションにパスプレフィックスを付与するプロキシを使う場合があります。
|
||||
|
||||
そのような場合は `root_path` でアプリケーションを設定できます。
|
||||
|
||||
`root_path` は(FastAPI が Starlette を通して基づいている)ASGI 仕様で提供されている仕組みです。
|
||||
|
||||
`root_path` はこの種のケースを扱うために使われます。
|
||||
|
||||
これはサブアプリケーションをマウントする際にも内部的に使用されます。
|
||||
|
||||
ここでいう「パスプレフィックスを削除するプロキシ」とは、コード上では `/app` というパスを宣言していても、その上にプロキシ層を追加して **FastAPI** アプリケーションを `/api/v1` のようなパスの下に配置することを指します。
|
||||
|
||||
この場合、元のパス `/app` は実際には `/api/v1/app` で提供されます。
|
||||
|
||||
すべてのコードは `/app` だけを前提に書かれているにもかかわらず、です。
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *}
|
||||
|
||||
そしてプロキシは、アプリサーバー(おそらく FastAPI CLI 経由の Uvicorn)に転送する前に、その場で **パスプレフィックス** を**「削除」**し、アプリケーション側には自分が `/app` で提供されているように見せかけます。これにより、コードのすべてを `/api/v1` のプレフィックス付きに書き換える必要がありません。
|
||||
|
||||
ここまでは通常どおりに動作します。
|
||||
|
||||
しかし、統合ドキュメント UI(フロントエンド)を開くと、OpenAPI スキーマを `/api/v1/openapi.json` ではなく `/openapi.json` から取得しようとします。
|
||||
|
||||
そのため、フロントエンド(ブラウザで動作)は `/openapi.json` にアクセスしようとして、OpenAPI スキーマを取得できません。
|
||||
|
||||
このアプリには `/api/v1` のパスプレフィックスを付与するプロキシがあるため、フロントエンドは `/api/v1/openapi.json` から取得する必要があります。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
|
||||
browser("Browser")
|
||||
proxy["Proxy on http://0.0.0.0:9999/api/v1/app"]
|
||||
server["Server on http://127.0.0.1:8000/app"]
|
||||
|
||||
browser --> proxy
|
||||
proxy --> server
|
||||
```
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
IP `0.0.0.0` は、そのマシン/サーバーで利用可能なすべての IP で待ち受けることを意味する表現として一般的に使われます。
|
||||
|
||||
///
|
||||
|
||||
ドキュメント UI では、この API の `server` が(プロキシの背後で)`/api/v1` にあることを宣言する OpenAPI スキーマも必要です。例えば:
|
||||
|
||||
```JSON hl_lines="4-8"
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
// ほかの項目
|
||||
"servers": [
|
||||
{
|
||||
"url": "/api/v1"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
// ほかの項目
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
この例では「Proxy」は **Traefik** のようなもの、サーバーは **Uvicorn** と FastAPI CLI で FastAPI アプリケーションを実行しているものを想定しています。
|
||||
|
||||
### `root_path` の指定 { #providing-the-root-path }
|
||||
|
||||
これを実現するには、次のようにコマンドラインオプション `--root-path` を使用します:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Hypercorn を使う場合も、同様に `--root-path` オプションがあります。
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
このユースケース向けに、ASGI 仕様は `root_path` を定義しています。
|
||||
|
||||
そして `--root-path` コマンドラインオプションは、その `root_path` を提供します。
|
||||
|
||||
///
|
||||
|
||||
### 現在の `root_path` の確認 { #checking-the-current-root-path }
|
||||
|
||||
各リクエストでアプリケーションが使用している現在の `root_path` は取得できます。これは(ASGI 仕様の一部である)`scope` 辞書に含まれます。
|
||||
|
||||
ここではデモのため、メッセージに含めています。
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *}
|
||||
|
||||
そのうえで、次のように Uvicorn を起動すると:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
レスポンスは次のようになります:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
}
|
||||
```
|
||||
|
||||
### FastAPI アプリでの `root_path` 設定 { #setting-the-root-path-in-the-fastapi-app }
|
||||
|
||||
あるいは、`--root-path` のようなコマンドラインオプションを渡せない場合は、FastAPI アプリ作成時にパラメータ `root_path` を設定できます:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *}
|
||||
|
||||
`FastAPI` に `root_path` を渡すのは、Uvicorn や Hypercorn にコマンドラインオプション `--root-path` を渡すのと同等です。
|
||||
|
||||
### `root_path` について { #about-root-path }
|
||||
|
||||
サーバー(Uvicorn)は、その `root_path` をアプリに渡す以外の用途では使用しない点に注意してください。
|
||||
|
||||
しかし、ブラウザで <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a> にアクセスすると、通常どおりのレスポンスが表示されます:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
}
|
||||
```
|
||||
|
||||
つまり、`http://127.0.0.1:8000/api/v1/app` でアクセスされることは想定していません。
|
||||
|
||||
Uvicorn は、プロキシが `http://127.0.0.1:8000/app` にアクセスしてくることを想定しており、その上に追加の `/api/v1` プレフィックスを付けるのはプロキシの責務です。
|
||||
|
||||
## パスプレフィックスを削除するプロキシについて { #about-proxies-with-a-stripped-path-prefix }
|
||||
|
||||
パスプレフィックスを削除するプロキシは、設定方法の一例にすぎない点に注意してください。
|
||||
|
||||
多くの場合、プロキシはパスプレフィックスを削除しない設定が既定でしょう。
|
||||
|
||||
そのような場合(パスプレフィックスを削除しない場合)は、プロキシは `https://myawesomeapp.com` のようなアドレスで待ち受け、ブラウザが `https://myawesomeapp.com/api/v1/app` にアクセスし、サーバー(例: Uvicorn)が `http://127.0.0.1:8000` で待ち受けているなら、プロキシ(プレフィックスを削除しない)は同じパス `http://127.0.0.1:8000/api/v1/app` で Uvicorn にアクセスします。
|
||||
|
||||
## Traefik を使ったローカル検証 { #testing-locally-with-traefik }
|
||||
|
||||
<a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a> を使えば、パスプレフィックスを削除する構成をローカルで簡単に試せます。
|
||||
|
||||
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">Traefik をダウンロード</a> してください。単一バイナリなので、圧縮ファイルを展開して端末から直接実行できます。
|
||||
|
||||
次の内容で `traefik.toml` というファイルを作成します:
|
||||
|
||||
```TOML hl_lines="3"
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":9999"
|
||||
|
||||
[providers]
|
||||
[providers.file]
|
||||
filename = "routes.toml"
|
||||
```
|
||||
|
||||
これは Traefik にポート 9999 で待ち受け、別のファイル `routes.toml` を使用するよう指示します。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
標準の HTTP ポート 80 ではなく 9999 を使うのは、管理者(`sudo`)権限で実行する必要をなくすためです。
|
||||
|
||||
///
|
||||
|
||||
次に、その `routes.toml` ファイルを作成します:
|
||||
|
||||
```TOML hl_lines="5 12 20"
|
||||
[http]
|
||||
[http.middlewares]
|
||||
|
||||
[http.middlewares.api-stripprefix.stripPrefix]
|
||||
prefixes = ["/api/v1"]
|
||||
|
||||
[http.routers]
|
||||
|
||||
[http.routers.app-http]
|
||||
entryPoints = ["http"]
|
||||
service = "app"
|
||||
rule = "PathPrefix(`/api/v1`)"
|
||||
middlewares = ["api-stripprefix"]
|
||||
|
||||
[http.services]
|
||||
|
||||
[http.services.app]
|
||||
[http.services.app.loadBalancer]
|
||||
[[http.services.app.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:8000"
|
||||
```
|
||||
|
||||
このファイルは Traefik に `/api/v1` のパスプレフィックスを使うよう設定します。
|
||||
|
||||
そして Traefik は、`http://127.0.0.1:8000` で動作している Uvicorn へリクエストを転送します。
|
||||
|
||||
では Traefik を起動します:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ ./traefik --configFile=traefik.toml
|
||||
|
||||
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
次に、`--root-path` オプションを指定してアプリを起動します:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### レスポンスの確認 { #check-the-responses }
|
||||
|
||||
ここで、Uvicorn のポートの URL <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a> にアクセスすると、通常どおりのレスポンスが表示されます:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
}
|
||||
```
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`http://127.0.0.1:8000/app` にアクセスしているにもかかわらず、オプション `--root-path` から取得した `root_path` が `/api/v1` と表示されている点に注目してください。
|
||||
|
||||
///
|
||||
|
||||
次に、Traefik のポートでプレフィックス付きの URL <a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a> を開きます。
|
||||
|
||||
同じレスポンスが得られます:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
}
|
||||
```
|
||||
|
||||
ただし今回は、プロキシが付与したプレフィックス `/api/v1` の付いた URL です。
|
||||
|
||||
もちろん、ここでの想定は全員がプロキシ経由でアプリにアクセスすることです。したがって、パスプレフィックス `/api/v1` のある版が「正しい」アクセス方法になります。
|
||||
|
||||
一方、プレフィックスのない版(`http://127.0.0.1:8000/app`。Uvicorn が直接提供)は、_プロキシ_(Traefik)専用の接続先になります。
|
||||
|
||||
これにより、プロキシ(Traefik)がパスプレフィックスをどのように用い、サーバー(Uvicorn)が `--root-path` の `root_path` をどのように利用するかが分かります。
|
||||
|
||||
### ドキュメント UI の確認 { #check-the-docs-ui }
|
||||
|
||||
ここがポイントです。✨
|
||||
|
||||
「公式な」アクセス方法は、定義したパスプレフィックス付きのプロキシ経由です。したがって想定どおり、プレフィックスなしの URL で Uvicorn が直接提供するドキュメント UI にアクセスすると動作しません。プロキシ経由でアクセスされることを前提としているためです。
|
||||
|
||||
<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> を確認してください:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image01.png">
|
||||
|
||||
しかし、プロキシ(ポート `9999`)を使った「公式」URL `/api/v1/docs` でドキュメント UI にアクセスすると、正しく動作します!🎉
|
||||
|
||||
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> を確認してください:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image02.png">
|
||||
|
||||
ねらいどおりです。✔️
|
||||
|
||||
これは、FastAPI が `root_path` を使って、OpenAPI の既定の `server` を `root_path` の URL で生成するためです。
|
||||
|
||||
## 追加のサーバー { #additional-servers }
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
これは高度なユースケースです。読み飛ばしても構いません。
|
||||
|
||||
///
|
||||
|
||||
既定では、**FastAPI** は OpenAPI スキーマ内に `root_path` の URL を持つ `server` を作成します。
|
||||
|
||||
しかし、ステージングと本番の両方と同じドキュメント UI で対話させたい場合など、別の `servers` を指定することもできます。
|
||||
|
||||
カスタムの `servers` リストを渡していて、かつ `root_path`(API がプロキシの背後にあるため)が設定されている場合、**FastAPI** はこの `root_path` を用いた「server」をリストの先頭に挿入します。
|
||||
|
||||
例えば:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *}
|
||||
|
||||
次のような OpenAPI スキーマが生成されます:
|
||||
|
||||
```JSON hl_lines="5-7"
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
// ほかの項目
|
||||
"servers": [
|
||||
{
|
||||
"url": "/api/v1"
|
||||
},
|
||||
{
|
||||
"url": "https://stag.example.com",
|
||||
"description": "Staging environment"
|
||||
},
|
||||
{
|
||||
"url": "https://prod.example.com",
|
||||
"description": "Production environment"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
// ほかの項目
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`root_path` から取得した `url` 値 `/api/v1` を持つ server が自動生成されている点に注目してください。
|
||||
|
||||
///
|
||||
|
||||
ドキュメント UI(<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a>)では次のように表示されます:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image03.png">
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
ドキュメント UI は、選択した server と対話します。
|
||||
|
||||
///
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
OpenAPI 仕様の `servers` プロパティは任意です。
|
||||
|
||||
`servers` パラメータを指定せず、かつ `root_path` が `/` の場合、生成される OpenAPI スキーマからは `servers` プロパティが既定で完全に省略されます。これは、`url` が `/` の server が 1 つあるのと同等です。
|
||||
|
||||
///
|
||||
|
||||
### `root_path` 由来の自動 server を無効化 { #disable-automatic-server-from-root-path }
|
||||
|
||||
`root_path` を用いた自動的な server を **FastAPI** に含めてほしくない場合は、パラメータ `root_path_in_servers=False` を使用します:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *}
|
||||
|
||||
すると、OpenAPI スキーマには含まれません。
|
||||
|
||||
## サブアプリケーションのマウント { #mounting-a-sub-application }
|
||||
|
||||
`root_path` を伴うプロキシを使用しつつサブアプリケーションをマウントする必要がある場合でも([サブアプリケーション - マウント](sub-applications.md){.internal-link target=_blank} 参照)、通常どおりに行えます。
|
||||
|
||||
FastAPI は内部で `root_path` を適切に扱うため、そのまま動作します。✨
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# Dataclasses の使用 { #using-dataclasses }
|
||||
|
||||
FastAPI は **Pydantic** の上に構築されており、これまでにリクエストやレスポンスを宣言するために Pydantic モデルを使う方法を紹介してきました。
|
||||
|
||||
しかし FastAPI は、同様の方法で <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a> もサポートします:
|
||||
|
||||
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
|
||||
|
||||
これは **Pydantic** によって引き続きサポートされています。Pydantic には <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">`dataclasses` の内部サポート</a> があるためです。
|
||||
|
||||
そのため、上記のように明示的に Pydantic を使っていないコードでも、FastAPI は標準の dataclass を Pydantic 独自の dataclass に変換するために Pydantic を使用しています。
|
||||
|
||||
そして当然ながら、次の点も同様にサポートされます:
|
||||
|
||||
- データ検証
|
||||
- データのシリアライズ
|
||||
- データのドキュメント化 など
|
||||
|
||||
これは Pydantic モデルの場合と同じように動作します。内部的にも同様に Pydantic を使って実現されています。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
dataclasses は、Pydantic モデルができることをすべては行えない点に留意してください。
|
||||
|
||||
そのため、Pydantic モデルを使う必要がある場合もあります。
|
||||
|
||||
しかし既存の dataclass が多数あるなら、FastAPI で Web API を構築する際にそれらを活用するちょっとしたテクニックになります。🤓
|
||||
|
||||
///
|
||||
|
||||
## `response_model` での dataclasses { #dataclasses-in-response-model }
|
||||
|
||||
`response_model` パラメータでも `dataclasses` を使用できます:
|
||||
|
||||
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
|
||||
|
||||
dataclass は自動的に Pydantic の dataclass に変換されます。
|
||||
|
||||
このため、そのスキーマは API ドキュメントの UI に表示されます:
|
||||
|
||||
<img src="/img/tutorial/dataclasses/image01.png">
|
||||
|
||||
## ネストしたデータ構造での dataclasses { #dataclasses-in-nested-data-structures }
|
||||
|
||||
`dataclasses` を他の型注釈と組み合わせて、ネストしたデータ構造を作成できます。
|
||||
|
||||
場合によっては、自動生成された API ドキュメントでエラーが発生するなどの理由で、Pydantic 版の `dataclasses` を使う必要があるかもしれません。
|
||||
|
||||
その場合は、標準の `dataclasses` を `pydantic.dataclasses` に置き換えるだけで済みます。これはドロップイン置換です:
|
||||
|
||||
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
|
||||
|
||||
1. 依然として標準の `dataclasses` から `field` をインポートします。
|
||||
|
||||
2. `pydantic.dataclasses` は `dataclasses` のドロップイン置換です。
|
||||
|
||||
3. `Author` dataclass は `Item` dataclass のリストを含みます。
|
||||
|
||||
4. `Author` dataclass を `response_model` パラメータとして使用しています。
|
||||
|
||||
5. リクエストボディとしての dataclass と併せて、他の標準の型注釈を使用できます。
|
||||
|
||||
この例では、`Item` dataclass のリストです。
|
||||
|
||||
6. ここでは、dataclass のリストである `items` を含む辞書を返しています。
|
||||
|
||||
FastAPI はデータを JSON に <dfn title="送信可能な形式にデータを変換すること">シリアライズ</dfn> できます。
|
||||
|
||||
7. ここでは `response_model` に `Author` dataclass のリストという型注釈を使用しています。
|
||||
|
||||
このように、`dataclasses` は標準の型注釈と組み合わせられます。
|
||||
|
||||
8. この *path operation 関数* は、`async def` ではなく通常の `def` を使用しています。
|
||||
|
||||
いつもどおり、FastAPI では必要に応じて `def` と `async def` を組み合わせられます。
|
||||
|
||||
どちらをいつ使うかの復習が必要な場合は、[`async` と `await`](../async.md#in-a-hurry){.internal-link target=_blank} に関するドキュメントの _"In a hurry?"_ セクションを参照してください。
|
||||
|
||||
9. この *path operation 関数* は(可能ではありますが)dataclass 自体は返さず、内部データを持つ辞書のリストを返しています。
|
||||
|
||||
FastAPI は dataclass を含む `response_model` パラメータを使ってレスポンスを変換します。
|
||||
|
||||
`dataclasses` は他の型注釈と多様な組み合わせが可能で、複雑なデータ構造を構成できます。
|
||||
|
||||
上記のコード内コメントのヒントを参照して、より具体的な詳細を確認してください。
|
||||
|
||||
## さらに学ぶ { #learn-more }
|
||||
|
||||
`dataclasses` を他の Pydantic モデルと組み合わせたり、継承したり、自分のモデルに含めたりもできます。
|
||||
|
||||
詳しくは、<a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">dataclasses に関する Pydantic ドキュメント</a> を参照してください。
|
||||
|
||||
## バージョン { #version }
|
||||
|
||||
これは FastAPI バージョン `0.67.0` 以降で利用可能です。🔖
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
# Lifespan イベント { #lifespan-events }
|
||||
|
||||
アプリケーションが起動する前に一度だけ実行すべきロジック(コード)を定義できます。これは、アプリケーションがリクエストを受け取り始める前に、そのコードが一度だけ実行される、という意味です。
|
||||
|
||||
同様に、アプリケーションがシャットダウンするときに実行すべきロジック(コード)も定義できます。この場合、そのコードは、(多くのリクエストを処理した)後に一度だけ実行されます。
|
||||
|
||||
このコードは、アプリケーションがリクエストの受け付けを「開始」する前、そして処理を「終了」した直後に実行されるため、アプリケーションの全体の「Lifespan」(この「lifespan」という言葉はすぐ後で重要になります 😉)をカバーします。
|
||||
|
||||
これは、アプリ全体で使用し、リクエスト間で「共有」し、かつ後で「クリーンアップ」する必要があるような「リソース」をセットアップするのにとても便利です。たとえば、データベース接続プールや、共有の機械学習モデルの読み込みなどです。
|
||||
|
||||
## ユースケース { #use-case }
|
||||
|
||||
まずはユースケースの例から始めて、これをどのように解決するかを見ていきます。
|
||||
|
||||
リクエストを処理するために使用したい「機械学習モデル」がいくつかあると想像してください。🤖
|
||||
|
||||
同じモデルをリクエスト間で共有するので、リクエストごとやユーザーごとに別々のモデルを使うわけではありません。
|
||||
|
||||
モデルの読み込みにはディスクから大量のデータを読む必要があり、かなり時間がかかるかもしれません。したがって、リクエストごとに読み込みたくはありません。
|
||||
|
||||
モジュール/ファイルのトップレベルで読み込むこともできますが、その場合は、たとえ簡単な自動テストを実行するだけでも「モデルを読み込む」ことになり、そのモデルの読み込みを待つ必要があるため、独立したコード部分を走らせるだけのテストでも「遅く」なってしまいます。
|
||||
|
||||
これを解決しましょう。リクエストを処理する前にモデルを読み込みますが、コードがロードされている最中ではなく、アプリケーションがリクエストの受け付けを開始する直前だけにします。
|
||||
|
||||
## Lifespan { #lifespan }
|
||||
|
||||
この「起動時」と「シャットダウン時」のロジックは、`FastAPI` アプリの `lifespan` パラメータと「コンテキストマネージャ」(これが何かはすぐに示します)を使って定義できます。
|
||||
|
||||
まずは例を見てから、詳細を説明します。
|
||||
|
||||
次のように、`yield` を使う非同期関数 `lifespan()` を作成します:
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *}
|
||||
|
||||
ここでは、`yield` の前で機械学習モデルの辞書に(ダミーの)モデル関数を入れることで、高コストな「起動時」のモデル読み込みをシミュレーションしています。このコードは、アプリケーションがリクエストを「受け付け始める前」に、すなわち起動時に実行されます。
|
||||
|
||||
そして `yield` の直後でモデルをアンロードします。このコードは、アプリケーションがリクエスト処理を「終了」した後、シャットダウン直前に実行されます。たとえばメモリや GPU のようなリソースを解放できます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`shutdown` は、アプリケーションを「停止」するときに発生します。
|
||||
|
||||
新しいバージョンを開始する必要があるか、単に実行をやめたくなったのかもしれません。🤷
|
||||
|
||||
///
|
||||
|
||||
### Lifespan 関数 { #lifespan-function }
|
||||
|
||||
まず注目すべきは、`yield` を使う非同期関数を定義していることです。これは「yield を使う依存関係(Dependencies)」にとてもよく似ています。
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *}
|
||||
|
||||
`yield` の前の前半は、アプリケーションが開始される「前」に実行されます。
|
||||
|
||||
`yield` の後半は、アプリケーションの処理が「終了」した「後」に実行されます。
|
||||
|
||||
### 非同期コンテキストマネージャ { #async-context-manager }
|
||||
|
||||
この関数には `@asynccontextmanager` がデコレートされています。
|
||||
|
||||
これにより、この関数は「非同期コンテキストマネージャ」になります。
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *}
|
||||
|
||||
Python の「コンテキストマネージャ」は、`with` 文で使えるものです。たとえば、`open()` はコンテキストマネージャとして使えます:
|
||||
|
||||
```Python
|
||||
with open("file.txt") as file:
|
||||
file.read()
|
||||
```
|
||||
|
||||
最近の Python には「非同期コンテキストマネージャ」もあります。`async with` で使います:
|
||||
|
||||
```Python
|
||||
async with lifespan(app):
|
||||
await do_stuff()
|
||||
```
|
||||
|
||||
このようにコンテキストマネージャ(または非同期コンテキストマネージャ)を作ると、`with` ブロックに入る前に `yield` より前のコードが実行され、`with` ブロックを出た後に `yield` より後ろのコードが実行されます。
|
||||
|
||||
上のコード例では直接それを使ってはいませんが、FastAPI に渡して内部で使ってもらいます。
|
||||
|
||||
`FastAPI` アプリの `lifespan` パラメータは「非同期コンテキストマネージャ」を受け取るので、新しく作った `lifespan` 非同期コンテキストマネージャを渡せます。
|
||||
|
||||
{* ../../docs_src/events/tutorial003_py310.py hl[22] *}
|
||||
|
||||
## 代替のイベント(非推奨) { #alternative-events-deprecated }
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
推奨される方法は、上で説明したとおり `FastAPI` アプリの `lifespan` パラメータを使って「起動」と「シャットダウン」を扱うことです。`lifespan` パラメータを指定すると、`startup` と `shutdown` のイベントハンドラは呼び出されなくなります。`lifespan` かイベントか、どちらか一方であり、両方同時ではありません。
|
||||
|
||||
この節は読み飛ばしてもかまいません。
|
||||
|
||||
///
|
||||
|
||||
起動時とシャットダウン時に実行されるロジックを定義する別の方法もあります。
|
||||
|
||||
アプリケーションが起動する前、またはシャットダウンするときに実行する必要があるイベントハンドラ(関数)を定義できます。
|
||||
|
||||
これらの関数は `async def` でも、通常の `def` でも構いません。
|
||||
|
||||
### `startup` イベント { #startup-event }
|
||||
|
||||
アプリケーションが開始される前に実行すべき関数を追加するには、イベント `"startup"` で宣言します:
|
||||
|
||||
{* ../../docs_src/events/tutorial001_py310.py hl[8] *}
|
||||
|
||||
この場合、`startup` のイベントハンドラ関数は items の「データベース」(単なる `dict`)をいくつかの値で初期化します。
|
||||
|
||||
イベントハンドラ関数は複数追加できます。
|
||||
|
||||
すべての `startup` イベントハンドラが完了するまで、アプリケーションはリクエストの受け付けを開始しません。
|
||||
|
||||
### `shutdown` イベント { #shutdown-event }
|
||||
|
||||
アプリケーションがシャットダウンするときに実行すべき関数を追加するには、イベント `"shutdown"` で宣言します:
|
||||
|
||||
{* ../../docs_src/events/tutorial002_py310.py hl[6] *}
|
||||
|
||||
ここでは、`shutdown` のイベントハンドラ関数が、テキスト行 `"Application shutdown"` をファイル `log.txt` に書き込みます。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`open()` 関数の `mode="a"` は「追加」(append)を意味します。つまり、そのファイルに既にある内容を上書きせず、行が後ろに追記されます。
|
||||
|
||||
///
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
この例では、ファイルを扱う標準の Python 関数 `open()` を使っています。
|
||||
|
||||
そのため、ディスクへの書き込みを「待つ」必要がある I/O(入力/出力)が関わります。
|
||||
|
||||
しかし `open()` 自体は `async` や `await` を使いません。
|
||||
|
||||
したがって、イベントハンドラ関数は `async def` ではなく通常の `def` で宣言しています。
|
||||
|
||||
///
|
||||
|
||||
### `startup` と `shutdown` をまとめて { #startup-and-shutdown-together }
|
||||
|
||||
起動時とシャットダウン時のロジックは関連していることが多いです。何かを開始してから終了したい、リソースを獲得してから解放したい、などです.
|
||||
|
||||
共有するロジックや変数のない別々の関数でそれを行うのは難しく、グローバル変数などに値を保存する必要が出てきます。
|
||||
|
||||
そのため、現在は上で説明したとおり `lifespan` を使うことが推奨されています。
|
||||
|
||||
## 技術詳細 { #technical-details }
|
||||
|
||||
技術が気になる方への細かな詳細です。🤓
|
||||
|
||||
内部的には、ASGI の技術仕様において、これは <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan プロトコル</a> の一部であり、`startup` と `shutdown` というイベントが定義されています。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
Starlette の `lifespan` ハンドラについては、<a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette の Lifespan ドキュメント</a>で詳しく読むことができます。
|
||||
|
||||
コードの他の領域で使える lifespan の状態をどのように扱うかも含まれています。
|
||||
|
||||
///
|
||||
|
||||
## サブアプリケーション { #sub-applications }
|
||||
|
||||
🚨 これらの lifespan イベント(startup と shutdown)はメインのアプリケーションに対してのみ実行され、[サブアプリケーション - マウント](sub-applications.md){.internal-link target=_blank} には実行されないことに注意してください。
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
# SDK の生成 { #generating-sdks }
|
||||
|
||||
**FastAPI** は **OpenAPI** 仕様に基づいているため、その API は多くのツールが理解できる標準形式で記述できます。
|
||||
|
||||
これにより、最新の**ドキュメント**、複数言語のクライアントライブラリ(<abbr title="Software Development Kits - ソフトウェア開発キット">**SDKs**</abbr>)、そしてコードと同期し続ける**テスト**や**自動化ワークフロー**を容易に生成できます。
|
||||
|
||||
本ガイドでは、FastAPI バックエンド向けの **TypeScript SDK** を生成する方法を説明します。
|
||||
|
||||
## オープソースの SDK ジェネレータ { #open-source-sdk-generators }
|
||||
|
||||
多用途な選択肢として <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a> があります。これは**多数のプログラミング言語**をサポートし、OpenAPI 仕様から SDK を生成できます。
|
||||
|
||||
**TypeScript クライアント**向けには、<a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a> が目的特化のソリューションで、TypeScript エコシステムに最適化された体験を提供します。
|
||||
|
||||
他の SDK ジェネレータは <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a> でも見つけられます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
FastAPI は自動的に **OpenAPI 3.1** の仕様を生成します。したがって、使用するツールはこのバージョンをサポートしている必要があります。
|
||||
|
||||
///
|
||||
|
||||
## FastAPI スポンサーによる SDK ジェネレータ { #sdk-generators-from-fastapi-sponsors }
|
||||
|
||||
このセクションでは、FastAPI をスポンサーしている企業による、**ベンチャー支援**および**企業支援**のソリューションを紹介します。これらの製品は、高品質な生成 SDK に加えて、**追加機能**や**統合**を提供します。
|
||||
|
||||
✨ [**FastAPI をスポンサーする**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ ことで、これらの企業はフレームワークとその**エコシステム**の健全性と**持続可能性**を支援しています。
|
||||
|
||||
この支援は、FastAPI の**コミュニティ**(皆さん)への強いコミットメントの表明でもあり、**優れたサービス**の提供だけでなく、堅牢で発展するフレームワーク FastAPI を支える姿勢を示しています。🙇
|
||||
|
||||
例えば、次のようなものがあります:
|
||||
|
||||
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
|
||||
* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
|
||||
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
|
||||
|
||||
これらのソリューションの中にはオープンソースや無料枠を提供するものもあり、金銭的コミットメントなしで試すことができます。他の商用 SDK ジェネレータも存在し、オンラインで見つけられます。🤓
|
||||
|
||||
## TypeScript SDK を作成する { #create-a-typescript-sdk }
|
||||
|
||||
まずは簡単な FastAPI アプリから始めます:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *}
|
||||
|
||||
ここで、*path operation* はリクエストとレスポンスのペイロードに使用するモデルを定義しており、`Item` と `ResponseMessage` を使っています。
|
||||
|
||||
### API ドキュメント { #api-docs }
|
||||
|
||||
`/docs` に移動すると、リクエストで送信・レスポンスで受信するデータの**スキーマ**が表示されます:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image01.png">
|
||||
|
||||
これらのスキーマは、アプリ内でモデルとして宣言されているため表示されます。
|
||||
|
||||
その情報はアプリの **OpenAPI スキーマ**に含まれ、API ドキュメントに表示されます。
|
||||
|
||||
OpenAPI に含まれるこれらのモデル情報を使って、**クライアントコードを生成**できます。
|
||||
|
||||
### Hey API { #hey-api }
|
||||
|
||||
モデルを備えた FastAPI アプリがあれば、Hey API で TypeScript クライアントを生成できます。最も手早い方法は npx を使うことです。
|
||||
|
||||
```sh
|
||||
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
|
||||
```
|
||||
|
||||
これで TypeScript SDK が `./src/client` に生成されます。
|
||||
|
||||
<a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">`@hey-api/openapi-ts` のインストール方法</a>や、<a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">生成物の詳細</a>は公式サイトを参照してください。
|
||||
|
||||
### SDK の利用 { #using-the-sdk }
|
||||
|
||||
これでクライアントコードを import して利用できます。例えば次のようになり、メソッドに対して補完が効きます:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image02.png">
|
||||
|
||||
送信するペイロードにも補完が適用されます:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image03.png">
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
FastAPI アプリの `Item` モデルで定義した `name` と `price` に補完が効いている点に注目してください。
|
||||
|
||||
///
|
||||
|
||||
送信データに対するインラインエラーも表示されます:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image04.png">
|
||||
|
||||
レスポンスオブジェクトにも補完があります:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image05.png">
|
||||
|
||||
## タグ付きの FastAPI アプリ { #fastapi-app-with-tags }
|
||||
|
||||
実運用ではアプリは大きくなり、*path operation* のグループ分けにタグを使うことが多いでしょう。
|
||||
|
||||
例えば **items** 用と **users** 用のセクションがあり、タグで分けられます:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *}
|
||||
|
||||
### タグ付き TypeScript クライアントの生成 { #generate-a-typescript-client-with-tags }
|
||||
|
||||
タグを用いた FastAPI アプリからクライアントを生成すると、通常クライアント側のコードもタグごとに分割されます。
|
||||
|
||||
これにより、クライアントコードも正しく整理・グルーピングされます:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image06.png">
|
||||
|
||||
この例では次のようになります:
|
||||
|
||||
* `ItemsService`
|
||||
* `UsersService`
|
||||
|
||||
### クライアントのメソッド名 { #client-method-names }
|
||||
|
||||
現状では、生成されるメソッド名(`createItemItemsPost` など)はあまりきれいではありません:
|
||||
|
||||
```TypeScript
|
||||
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
|
||||
```
|
||||
|
||||
これは、クライアントジェネレータが各 *path operation* の OpenAPI 内部の **operation ID** を用いるためです。
|
||||
|
||||
OpenAPI では operation ID は全ての *path operation* を通して一意である必要があります。そのため FastAPI は**関数名**、**パス**、**HTTP メソッド/オペレーション**を組み合わせて operation ID を生成し、一意性を保証します。
|
||||
|
||||
次にこれを改善する方法を示します。🤓
|
||||
|
||||
## カスタム operation ID とより良いメソッド名 { #custom-operation-ids-and-better-method-names }
|
||||
|
||||
operation ID の**生成方法**を**変更**して簡潔にし、クライアント側の**メソッド名をシンプル**にできます。
|
||||
|
||||
この場合でも各 operation ID が**一意**であることは別の方法で保証する必要があります。
|
||||
|
||||
例えば、各 *path operation* にタグを付け、**タグ**と *path operation* の**名前**(関数名)から operation ID を生成できます。
|
||||
|
||||
### 一意 ID 生成関数のカスタマイズ { #custom-generate-unique-id-function }
|
||||
|
||||
FastAPI は各 *path operation* に**一意 ID**を用いており、これは **operation ID** のほか、必要に応じてリクエストやレスポンスのカスタムモデル名にも使われます。
|
||||
|
||||
この関数はカスタマイズ可能です。`APIRoute` を受け取り、文字列を返します。
|
||||
|
||||
例えばここでは、最初のタグ(通常は 1 つ)と *path operation* 名(関数名)を使います。
|
||||
|
||||
そのカスタム関数を **FastAPI** の `generate_unique_id_function` パラメータに渡します:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *}
|
||||
|
||||
### カスタム operation ID で TypeScript クライアントを生成 { #generate-a-typescript-client-with-custom-operation-ids }
|
||||
|
||||
この状態でクライアントを再生成すると、メソッド名が改善されています:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image07.png">
|
||||
|
||||
ご覧のとおり、メソッド名はタグ名と関数名のみになり、URL パスや HTTP オペレーションの情報は含まれません。
|
||||
|
||||
### クライアント生成向けの OpenAPI 仕様の前処理 { #preprocess-the-openapi-specification-for-the-client-generator }
|
||||
|
||||
それでも生成コードには**重複情報**が残っています。
|
||||
|
||||
`ItemsService`(タグ由来)から items 関連であることはすでに分かるのに、メソッド名にもタグ名が前置されています。😕
|
||||
|
||||
OpenAPI 全体としては operation ID の**一意性**のために、このプレフィックスを維持したい場合があるでしょう。
|
||||
|
||||
しかし生成クライアント用には、クライアントを生成する直前に OpenAPI の operation ID を**加工**して、メソッド名をより**見やすく**、**クリーン**にできます。
|
||||
|
||||
OpenAPI の JSON を `openapi.json` として保存し、次のようなスクリプトで**そのタグのプレフィックスを除去**できます:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial004_py310.py *}
|
||||
|
||||
//// tab | Node.js
|
||||
|
||||
```Javascript
|
||||
{!> ../../docs_src/generate_clients/tutorial004.js!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
これにより operation ID は `items-get_items` のような形から単なる `get_items` に置き換わり、クライアントジェネレータはより簡潔なメソッド名を生成できます。
|
||||
|
||||
### 前処理済み OpenAPI から TypeScript クライアントを生成 { #generate-a-typescript-client-with-the-preprocessed-openapi }
|
||||
|
||||
生成元が `openapi.json` になったので、入力の場所を更新します:
|
||||
|
||||
```sh
|
||||
npx @hey-api/openapi-ts -i ./openapi.json -o src/client
|
||||
```
|
||||
|
||||
新しいクライアントを生成すると、**クリーンなメソッド名**になり、**補完**や**インラインエラー**などもそのまま利用できます:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image08.png">
|
||||
|
||||
## 利点 { #benefits }
|
||||
|
||||
自動生成されたクライアントを使うと、次のような対象で**補完**が得られます:
|
||||
|
||||
* メソッド
|
||||
* 本体のリクエストペイロード、クエリパラメータ等
|
||||
* レスポンスのペイロード
|
||||
|
||||
また、あらゆる箇所で**インラインエラー**も得られます。
|
||||
|
||||
バックエンドコードを更新してフロントエンドを**再生成**すれば、新しい *path operation* はメソッドとして追加され、古いものは削除され、その他の変更も生成コードに反映されます。🤓
|
||||
|
||||
つまり、変更があれば自動的にクライアントコードに**反映**されます。クライアントを**ビルド**すれば、使用データに**不整合**があればエラーになります。
|
||||
|
||||
その結果、多くのエラーを開発の初期段階で**早期発見**でき、本番で最終ユーザーに不具合が現れてから原因をデバッグする必要がなくなります。✨
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# 高度なミドルウェア { #advanced-middleware }
|
||||
|
||||
メインのチュートリアルでは、アプリケーションに[カスタムミドルウェア](../tutorial/middleware.md){.internal-link target=_blank}を追加する方法を学びました。
|
||||
|
||||
そして、[`CORSMiddleware` を使った CORS の扱い方](../tutorial/cors.md){.internal-link target=_blank}も学びました。
|
||||
|
||||
このセクションでは、その他のミドルウェアの使い方を見ていきます。
|
||||
|
||||
## ASGI ミドルウェアの追加 { #adding-asgi-middlewares }
|
||||
|
||||
**FastAPI** は Starlette を基盤としており、<abbr title="Asynchronous Server Gateway Interface - 非同期サーバーゲートウェイインターフェース">ASGI</abbr> 仕様を実装しているため、任意の ASGI ミドルウェアを利用できます。
|
||||
|
||||
ミドルウェアは ASGI 仕様に従っていれば、FastAPI や Starlette 専用に作られていなくても動作します。
|
||||
|
||||
一般に、ASGI ミドルウェアは最初の引数として ASGI アプリを受け取るクラスです。
|
||||
|
||||
そのため、サードパーティの ASGI ミドルウェアのドキュメントでは、おそらく次のように書かれているでしょう:
|
||||
|
||||
```Python
|
||||
from unicorn import UnicornMiddleware
|
||||
|
||||
app = SomeASGIApp()
|
||||
|
||||
new_app = UnicornMiddleware(app, some_config="rainbow")
|
||||
```
|
||||
|
||||
しかし FastAPI(正確には Starlette)は、内部ミドルウェアがサーバーエラーを処理し、カスタム例外ハンドラが正しく動作することを保証する、より簡単な方法を提供しています。
|
||||
|
||||
そのためには(CORS の例と同様に)`app.add_middleware()` を使います。
|
||||
|
||||
```Python
|
||||
from fastapi import FastAPI
|
||||
from unicorn import UnicornMiddleware
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.add_middleware(UnicornMiddleware, some_config="rainbow")
|
||||
```
|
||||
|
||||
`app.add_middleware()` は、最初の引数にミドルウェアのクラスを取り、それ以外の追加引数はミドルウェアに渡されます。
|
||||
|
||||
## 組み込みミドルウェア { #integrated-middlewares }
|
||||
|
||||
**FastAPI** は一般的なユースケースに対応するいくつかのミドルウェアを含んでいます。以下でその使い方を見ていきます。
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
以下の例では、`from starlette.middleware.something import SomethingMiddleware` を使うこともできます。
|
||||
|
||||
**FastAPI** は開発者であるあなたの便宜のために `fastapi.middleware` にいくつかのミドルウェアを提供しています。しかし、利用可能なミドルウェアの多くは Starlette から直接提供されています。
|
||||
|
||||
///
|
||||
|
||||
## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
|
||||
|
||||
すべての受信リクエストが `https` または `wss` でなければならないように強制します。
|
||||
|
||||
`http` または `ws` への受信リクエストは、安全なスキームにリダイレクトされます。
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *}
|
||||
|
||||
## `TrustedHostMiddleware` { #trustedhostmiddleware }
|
||||
|
||||
HTTP Host Header 攻撃を防ぐため、すべての受信リクエストに正しく設定された `Host` ヘッダーを強制します。
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *}
|
||||
|
||||
サポートされる引数は次のとおりです:
|
||||
|
||||
- `allowed_hosts` - 許可するホスト名のドメイン名リスト。`*.example.com` のようなワイルドカードドメインでサブドメインのマッチングもサポートします。任意のホスト名を許可するには、`allowed_hosts=["*"]` を使うか、このミドルウェアを省略します。
|
||||
- `www_redirect` - True に設定すると、許可されたホストの非 www 版へのリクエストを www 版へリダイレクトします。デフォルトは `True` です。
|
||||
|
||||
受信リクエストが正しく検証されない場合、`400` のレスポンスが返されます。
|
||||
|
||||
## `GZipMiddleware` { #gzipmiddleware }
|
||||
|
||||
`Accept-Encoding` ヘッダーに "gzip" を含むリクエストに対して GZip レスポンスを処理します。
|
||||
|
||||
このミドルウェアは、通常のレスポンスとストリーミングレスポンスの両方を処理します。
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *}
|
||||
|
||||
サポートされる引数は次のとおりです:
|
||||
|
||||
- `minimum_size` - このバイト数の最小サイズ未満のレスポンスは GZip 圧縮しません。デフォルトは `500` です。
|
||||
- `compresslevel` - GZip 圧縮時に使用します。1 から 9 までの整数です。デフォルトは `9`。値が小さいほど圧縮は速くなりますがファイルサイズは大きくなり、値が大きいほど圧縮は遅くなりますがファイルサイズは小さくなります。
|
||||
|
||||
## その他のミドルウェア { #other-middlewares }
|
||||
|
||||
他にも多くの ASGI ミドルウェアがあります。
|
||||
|
||||
例えば:
|
||||
|
||||
- <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn の `ProxyHeadersMiddleware`</a>
|
||||
- <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
|
||||
|
||||
他に利用可能なミドルウェアについては、<a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette のミドルウェアドキュメント</a>や <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome List</a> を参照してください。
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
# OpenAPI コールバック { #openapi-callbacks }
|
||||
|
||||
あなたは、*path operation* を持つ API を作成し、他者(多くの場合、あなたの API を「利用する」同一の開発者)が作成した *外部 API* へリクエストをトリガーできるようにできます。
|
||||
|
||||
あなたの API アプリが *外部 API* を呼び出すときに起きる処理は「コールバック」と呼ばれます。なぜなら、外部開発者が作成したソフトウェアがあなたの API にリクエストを送り、その後であなたの API が「呼び返し」、*外部 API*(おそらく同じ開発者が作成)へリクエストを送るためです。
|
||||
|
||||
この場合、その *外部 API* がどのようである「べき」かをドキュメント化したくなるでしょう。どんな *path operation* を持ち、どんなボディを受け取り、どんなレスポンスを返すか、などです。
|
||||
|
||||
## コールバックのあるアプリ { #an-app-with-callbacks }
|
||||
|
||||
例で見ていきます。
|
||||
|
||||
あなたが請求書を作成できるアプリを開発していると想像してください。
|
||||
|
||||
これらの請求書は `id`、`title`(任意)、`customer`、`total` を持ちます。
|
||||
|
||||
あなたの API の利用者(外部開発者)は、POST リクエストであなたの API に請求書を作成します。
|
||||
|
||||
その後、あなたの API は(仮にこうしましょう):
|
||||
|
||||
* 外部開発者の顧客に請求書を送ります。
|
||||
* 代金を回収します。
|
||||
* API 利用者(外部開発者)に通知を送り返します。
|
||||
* これは(あなたの API から)外部開発者が提供する *外部 API* に POST リクエストを送ることで行われます(これが「コールバック」です)。
|
||||
|
||||
## 通常の FastAPI アプリ { #the-normal-fastapi-app }
|
||||
|
||||
まず、コールバックを追加する前の通常の API アプリがどうなるか見てみましょう。
|
||||
|
||||
`Invoice` ボディを受け取り、クエリパラメータ `callback_url` にコールバック用の URL を含める *path operation* を持ちます。
|
||||
|
||||
この部分はとても普通で、ほとんどのコードはすでに見覚えがあるはずです:
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`callback_url` クエリパラメータは、Pydantic の <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a> 型を使用します。
|
||||
|
||||
///
|
||||
|
||||
唯一の新しい点は、*path operation デコレータ*の引数として `callbacks=invoices_callback_router.routes` を渡すことです。これが何かは次で見ます。
|
||||
|
||||
## コールバックのドキュメント化 { #documenting-the-callback }
|
||||
|
||||
実際のコールバックのコードは、あなた自身の API アプリに大きく依存します。
|
||||
|
||||
そしてアプリごとに大きく異なるでしょう。
|
||||
|
||||
それは次のように 1、2 行のコードかもしれません:
|
||||
|
||||
```Python
|
||||
callback_url = "https://example.com/api/v1/invoices/events/"
|
||||
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
```
|
||||
|
||||
しかし、おそらくコールバックで最も重要な点は、あなたの API 利用者(外部開発者)が、*あなたの API* がコールバックのリクエストボディなどで送るデータに従って、*外部 API* を正しく実装することを確実にすることです。
|
||||
|
||||
そこで次に行うのは、*あなたの API* からのコールバックを受け取るために、その *外部 API* がどうあるべきかをドキュメント化するコードを追加することです。
|
||||
|
||||
そのドキュメントはあなたの API の `/docs` の Swagger UI に表示され、外部開発者に *外部 API* の作り方を知らせます。
|
||||
|
||||
この例ではコールバック自体は実装しません(それは 1 行のコードでもよいでしょう)。ドキュメント部分のみです。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
実際のコールバックは単なる HTTP リクエストです。
|
||||
|
||||
自分でコールバックを実装する場合は、<a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> や <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a> のようなものを使えます。
|
||||
|
||||
///
|
||||
|
||||
## コールバックのドキュメント用コードを書く { #write-the-callback-documentation-code }
|
||||
|
||||
このコードはあなたのアプリで実行されません。*外部 API* がどうあるべきかをドキュメント化するためだけに必要です。
|
||||
|
||||
しかし、あなたはすでに **FastAPI** で API の自動ドキュメントを簡単に作る方法を知っています。
|
||||
|
||||
その知識を使って、*外部 API* がどうあるべきかをドキュメント化します……つまり、外部 API が実装すべき *path operation(s)*(あなたの API が呼び出すもの)を作成します。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
コールバックをドキュメント化するコードを書くときは、あなたがその「外部開発者」だと想像するのが役に立つかもしれません。いま実装しているのは「あなたの API」ではなく、*外部 API* です。
|
||||
|
||||
この(外部開発者の)視点を一時的に採用すると、その *外部 API* に対してパラメータ、ボディ用の Pydantic モデル、レスポンスなどをどこに置くのが自然かがより明確に感じられるでしょう。
|
||||
|
||||
///
|
||||
|
||||
### コールバック用 APIRouter を作成 { #create-a-callback-apirouter }
|
||||
|
||||
まず、1 つ以上のコールバックを含む新しい `APIRouter` を作成します。
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
|
||||
|
||||
### コールバックの path operation を作成 { #create-the-callback-path-operation }
|
||||
|
||||
上で作成したのと同じ `APIRouter` を使って、コールバックの *path operation* を作成します。
|
||||
|
||||
見た目は通常の FastAPI の *path operation* と同じです:
|
||||
|
||||
* 受け取るボディの宣言(例: `body: InvoiceEvent`)が必要でしょう。
|
||||
* 返すレスポンスの宣言(例: `response_model=InvoiceEventReceived`)も持てます。
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
|
||||
|
||||
通常の *path operation* と異なる主な点が 2 つあります:
|
||||
|
||||
* 実際のコードは不要です。あなたのアプリはこのコードを決して呼びません。これは *外部 API* をドキュメント化するためだけに使われます。したがって、関数本体は `pass` で構いません。
|
||||
* *パス* には、*あなたの API* に送られた元のリクエストのパラメータや一部を変数として使える <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 の式</a>(後述)を含められます。
|
||||
|
||||
### コールバックのパス式 { #the-callback-path-expression }
|
||||
|
||||
コールバックの *パス* には、*あなたの API* に送られた元のリクエストの一部を含められる <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 の式</a>を使用できます。
|
||||
|
||||
この例では、`str` は次のとおりです:
|
||||
|
||||
```Python
|
||||
"{$callback_url}/invoices/{$request.body.id}"
|
||||
```
|
||||
|
||||
つまり、あなたの API 利用者(外部開発者)が *あなたの API* に次のようにリクエストを送った場合:
|
||||
|
||||
```
|
||||
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
|
||||
```
|
||||
|
||||
JSON ボディは:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"id": "2expen51ve",
|
||||
"customer": "Mr. Richie Rich",
|
||||
"total": "9999"
|
||||
}
|
||||
```
|
||||
|
||||
その後 *あなたの API* は請求書を処理し、のちほど `callback_url`(*外部 API*)へコールバックのリクエストを送ります:
|
||||
|
||||
```
|
||||
https://www.external.org/events/invoices/2expen51ve
|
||||
```
|
||||
|
||||
JSON ボディは次のような内容です:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"description": "Payment celebration",
|
||||
"paid": true
|
||||
}
|
||||
```
|
||||
|
||||
そして *外部 API* からは次のような JSON ボディのレスポンスを期待します:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
使用されるコールバック URL には、クエリパラメータ `callback_url`(`https://www.external.org/events`)で受け取った URL と、JSON ボディ内の請求書 `id`(`2expen51ve`)が含まれている点に注目してください。
|
||||
|
||||
///
|
||||
|
||||
### コールバック用ルーターを追加 { #add-the-callback-router }
|
||||
|
||||
これで、上で作成したコールバック用ルーター内に、必要なコールバックの *path operation(s)*(*外部開発者* が *外部 API* に実装すべきもの)が用意できました。
|
||||
|
||||
次に、*あなたの API の path operation デコレータ*の `callbacks` パラメータに、そのコールバック用ルーターの属性 `.routes`(実体はルート/*path operations* の `list`)を渡します:
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`callback=` に渡すのはルーター本体(`invoices_callback_router`)ではなく、属性 `.routes`(`invoices_callback_router.routes`)である点に注意してください。
|
||||
|
||||
///
|
||||
|
||||
### ドキュメントを確認 { #check-the-docs }
|
||||
|
||||
アプリを起動して <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> にアクセスします。
|
||||
|
||||
あなたの *path operation* に「Callbacks」セクションが含まれ、*外部 API* がどうあるべきかが表示されているのが確認できます:
|
||||
|
||||
<img src="/img/tutorial/openapi-callbacks/image01.png">
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# OpenAPI の Webhook { #openapi-webhooks }
|
||||
|
||||
アプリがある種の**イベント**を**通知**するために、データ付きで相手のアプリ(リクエスト送信)を呼び出す可能性があることを、API の**ユーザー**に伝えたい場合があります。
|
||||
|
||||
これは、通常のようにユーザーがあなたの API にリクエストを送るのではなく、**あなたの API(あなたのアプリ)**が**相手のシステム**(相手の API、アプリ)にリクエストを送る、ということです。
|
||||
|
||||
これは一般に**Webhook**と呼ばれます。
|
||||
|
||||
## Webhook の手順 { #webhooks-steps }
|
||||
|
||||
通常の流れとして、まずあなたのコード内で、送信するメッセージ、すなわちリクエストの**本文(ボディ)**を**定義**します。
|
||||
|
||||
加えて、アプリがそれらのリクエスト(イベント)を送信する**タイミング**も何らかの形で定義します。
|
||||
|
||||
そして**ユーザー**は、アプリがそのリクエストを送るべき**URL**を(たとえばどこかの Web ダッシュボードで)定義します。
|
||||
|
||||
Webhook の URL を登録する方法や実際にリクエストを送るコードなど、これらの**ロジック**はすべてあなた次第です。**あなた自身のコード**で好きなように実装します。
|
||||
|
||||
## FastAPI と OpenAPI による Webhook のドキュメント化 { #documenting-webhooks-with-fastapi-and-openapi }
|
||||
|
||||
**FastAPI** と OpenAPI を使うと、Webhook の名前、アプリが送信できる HTTP の操作(例: `POST`, `PUT` など)、アプリが送るリクエストの**ボディ**を定義できます。
|
||||
|
||||
これにより、ユーザーがあなたの **Webhook** リクエストを受け取るための**API を実装**するのが大幅に簡単になります。場合によっては、ユーザーが自分たちの API コードを自動生成できるかもしれません。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
Webhook は OpenAPI 3.1.0 以上で利用可能で、FastAPI `0.99.0` 以上が対応しています。
|
||||
|
||||
///
|
||||
|
||||
## Webhook を持つアプリ { #an-app-with-webhooks }
|
||||
|
||||
**FastAPI** アプリケーションを作成すると、`webhooks` という属性があり、ここで *path operations* と同様に(例: `@app.webhooks.post()`)*webhook* を定義できます。
|
||||
|
||||
{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *}
|
||||
|
||||
定義した webhook は **OpenAPI** スキーマおよび自動生成される **ドキュメント UI** に反映されます。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`app.webhooks` オブジェクトは実際には単なる `APIRouter` で、複数ファイルでアプリを構成する際に使うものと同じ型です。
|
||||
|
||||
///
|
||||
|
||||
Webhook では(`/items/` のような)*パス*を宣言しているわけではない点に注意してください。ここで渡す文字列は webhook の**識別子**(イベント名)です。たとえば `@app.webhooks.post("new-subscription")` での webhook 名は `new-subscription` です。
|
||||
|
||||
これは、**ユーザー**が実際に Webhook リクエストを受け取りたい**URL パス**を、別の方法(例: Web ダッシュボード)で定義することを想定しているためです。
|
||||
|
||||
### ドキュメントの確認 { #check-the-docs }
|
||||
|
||||
アプリを起動し、<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> にアクセスします。
|
||||
|
||||
ドキュメントには通常の *path operations* に加えて、**webhooks** も表示されます:
|
||||
|
||||
<img src="/img/tutorial/openapi-webhooks/image01.png">
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# レスポンス - ステータスコードの変更 { #response-change-status-code }
|
||||
|
||||
すでに、デフォルトの[レスポンスのステータスコード](../tutorial/response-status-code.md){.internal-link target=_blank}を設定できることをご存知かもしれません。
|
||||
|
||||
しかし場合によっては、デフォルトとは異なるステータスコードを返す必要があります。
|
||||
|
||||
## ユースケース { #use-case }
|
||||
|
||||
たとえば、デフォルトでは HTTP ステータスコード "OK" `200` を返したいとします。
|
||||
|
||||
しかし、データが存在しなければそれを作成し、HTTP ステータスコード "CREATED" `201` を返したい。
|
||||
|
||||
それでも、返すデータは `response_model` でフィルタ・変換できるようにしておきたい。
|
||||
|
||||
そのような場合は `Response` パラメータを使えます。
|
||||
|
||||
## `Response` パラメータを使う { #use-a-response-parameter }
|
||||
|
||||
*path operation* 関数で `Response` 型のパラメータを宣言できます(Cookie やヘッダーと同様です)。
|
||||
|
||||
そして、その*一時的な*レスポンスオブジェクトに `status_code` を設定できます。
|
||||
|
||||
{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *}
|
||||
|
||||
その後は通常どおり、必要な任意のオブジェクト(`dict`、データベースモデルなど)を返せます。
|
||||
|
||||
そして `response_model` を宣言していれば、返したオブジェクトのフィルタと変換には引き続きそれが使われます。
|
||||
|
||||
FastAPI はその*一時的な*レスポンスからステータスコード(および Cookie とヘッダー)を取り出し、`response_model` によってフィルタ済みの返却値を含む最終的なレスポンスに反映します。
|
||||
|
||||
また、`Response` パラメータは依存関係内に宣言してステータスコードを設定することもできます。ただし、最後に設定されたものが優先される点に注意してください。
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# レスポンスの Cookie { #response-cookies }
|
||||
|
||||
## `Response` パラメータを使う { #use-a-response-parameter }
|
||||
|
||||
*path operation 関数*で `Response` 型のパラメータを宣言できます。
|
||||
|
||||
そして、その*一時的*なレスポンスオブジェクトに Cookie を設定できます。
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *}
|
||||
|
||||
その後は通常どおり、必要な任意のオブジェクト(`dict`、データベースモデルなど)を返せます。
|
||||
|
||||
`response_model` を宣言している場合でも、返したオブジェクトは引き続きフィルタおよび変換されます。
|
||||
|
||||
**FastAPI** はその*一時的*なレスポンスから Cookie(およびヘッダーやステータスコード)を取り出し、`response_model` によってフィルタされた返却値を含む最終的なレスポンスに設定します。
|
||||
|
||||
`Response` パラメータは依存関係でも宣言でき、そこで Cookie(やヘッダー)を設定することも可能です。
|
||||
|
||||
## `Response` を直接返す { #return-a-response-directly }
|
||||
|
||||
コードで `Response` を直接返すときに、Cookie を作成することもできます。
|
||||
|
||||
そのためには、[Response を直接返す](response-directly.md){.internal-link target=_blank} で説明されているとおりにレスポンスを作成します。
|
||||
|
||||
そのレスポンスに Cookie を設定してから返します:
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`Response` パラメータを使わずにレスポンスを直接返す場合、FastAPI はそのレスポンスをそのまま返します。
|
||||
|
||||
そのため、データの型が正しいことを確認する必要があります。例えば、`JSONResponse` を返すなら、JSON と互換性がある必要があります。
|
||||
|
||||
また、`response_model` によってフィルタされるべきデータを送っていないことも確認してください。
|
||||
|
||||
///
|
||||
|
||||
### 詳細情報 { #more-info }
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
`from starlette.responses import Response` や `from starlette.responses import JSONResponse` を使うこともできます。
|
||||
|
||||
**FastAPI** は開発者の利便性のために、`starlette.responses` と同じものを `fastapi.responses` として提供しています。ただし、利用可能なレスポンスの大半は Starlette から直接提供されています。
|
||||
|
||||
また、`Response` はヘッダーや Cookie の設定に頻繁に使われるため、`fastapi.Response` としても提供されています。
|
||||
|
||||
///
|
||||
|
||||
利用可能なすべてのパラメータやオプションについては、<a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette のドキュメント</a>を参照してください。
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# レスポンスヘッダー { #response-headers }
|
||||
|
||||
## `Response` パラメータを使う { #use-a-response-parameter }
|
||||
|
||||
(Cookie と同様に)*path operation 関数*で `Response` 型のパラメータを宣言できます。
|
||||
|
||||
そして、その*一時的*なレスポンスオブジェクトにヘッダーを設定できます。
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *}
|
||||
|
||||
その後は通常どおり、必要な任意のオブジェクト(`dict`、データベースモデルなど)を返せます。
|
||||
|
||||
`response_model` を宣言している場合は、返したオブジェクトのフィルタと変換に引き続き使用されます。
|
||||
|
||||
**FastAPI** はその*一時的*なレスポンスからヘッダー(Cookie やステータスコードも含む)を取り出し、`response_model` によってフィルタされた返却値を含む最終的なレスポンスに反映します。
|
||||
|
||||
また、依存関係の中で `Response` パラメータを宣言し、その中でヘッダー(や Cookie)を設定することもできます。
|
||||
|
||||
## `Response` を直接返す { #return-a-response-directly }
|
||||
|
||||
`Response` を直接返す場合にもヘッダーを追加できます。
|
||||
|
||||
[Response を直接返す](response-directly.md){.internal-link target=_blank} で説明したようにレスポンスを作成し、ヘッダーを追加のパラメータとして渡します:
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *}
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
`from starlette.responses import Response` や `from starlette.responses import JSONResponse` を使うこともできます。
|
||||
|
||||
**FastAPI** は、開発者であるあなたへの便宜として、`starlette.responses` と同じものを `fastapi.responses` として提供しています。しかし、利用可能なレスポンスの大半は直接 Starlette から来ています。
|
||||
|
||||
また、`Response` はヘッダーや Cookie を設定するのによく使われるため、**FastAPI** は `fastapi.Response` でも提供しています。
|
||||
|
||||
///
|
||||
|
||||
## カスタムヘッダー { #custom-headers }
|
||||
|
||||
独自のカスタムヘッダーは、<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">`X-` プレフィックスを使って</a>追加できることに注意してください。
|
||||
|
||||
ただし、ブラウザのクライアントに見えるようにしたいカスタムヘッダーがある場合は、CORS 設定にそれらを追加する必要があります([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank} を参照)。このとき、<a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette の CORS ドキュメント</a>に記載の `expose_headers` パラメータを使用します。
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
# HTTP Basic 認証 { #http-basic-auth }
|
||||
|
||||
最もシンプルなケースでは、HTTP Basic 認証を利用できます。
|
||||
|
||||
HTTP Basic 認証では、アプリケーションはユーザー名とパスワードを含むヘッダーを期待します。
|
||||
|
||||
それを受け取れない場合、HTTP 401 "Unauthorized" エラーを返します。
|
||||
|
||||
そして、値が `Basic` のヘッダー `WWW-Authenticate` を、任意の `realm` パラメータとともに返します。
|
||||
|
||||
これにより、ブラウザは組み込みのユーザー名とパスワード入力プロンプトを表示します。
|
||||
|
||||
その後、そのユーザー名とパスワードを入力すると、ブラウザはそれらをヘッダーに自動的に付与して送信します。
|
||||
|
||||
## シンプルな HTTP Basic 認証 { #simple-http-basic-auth }
|
||||
|
||||
- `HTTPBasic` と `HTTPBasicCredentials` をインポートします。
|
||||
- `HTTPBasic` を使って「`security` スキーム」を作成します。
|
||||
- その `security` を依存関係として path operation に使用します。
|
||||
- `HTTPBasicCredentials` 型のオブジェクトが返ります:
|
||||
- 送信された `username` と `password` を含みます。
|
||||
|
||||
{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *}
|
||||
|
||||
URL を最初に開こうとしたとき(またはドキュメントで「Execute」ボタンをクリックしたとき)、ブラウザはユーザー名とパスワードの入力を求めます:
|
||||
|
||||
<img src="/img/tutorial/security/image12.png">
|
||||
|
||||
## ユーザー名の確認 { #check-the-username }
|
||||
|
||||
より完全な例です。
|
||||
|
||||
依存関係を使ってユーザー名とパスワードが正しいかを確認します。
|
||||
|
||||
これには、Python 標準モジュール <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> を用いてユーザー名とパスワードを検証します。
|
||||
|
||||
`secrets.compare_digest()` は `bytes` か、ASCII 文字(英語の文字)のみを含む `str` を受け取る必要があります。つまり、`Sebastián` のように `á` を含む文字ではそのままでは動作しません。
|
||||
|
||||
これに対処するため、まず `username` と `password` を UTF-8 でエンコードして `bytes` に変換します。
|
||||
|
||||
そのうえで、`secrets.compare_digest()` を使って、`credentials.username` が `"stanleyjobson"` であり、`credentials.password` が `"swordfish"` であることを確認します。
|
||||
|
||||
{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *}
|
||||
|
||||
これは次のようなコードに相当します:
|
||||
|
||||
```Python
|
||||
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
|
||||
# Return some error
|
||||
...
|
||||
```
|
||||
|
||||
しかし `secrets.compare_digest()` を使うことで、「タイミング攻撃」と呼ばれる種類の攻撃に対して安全になります。
|
||||
|
||||
### タイミング攻撃 { #timing-attacks }
|
||||
|
||||
「タイミング攻撃」とは何でしょうか?
|
||||
|
||||
攻撃者がユーザー名とパスワードを推測しようとしていると想像してください。
|
||||
|
||||
そして、ユーザー名 `johndoe`、パスワード `love123` を使ってリクエストを送ります。
|
||||
|
||||
その場合、アプリケーション内の Python コードは次のようなものと等価になります:
|
||||
|
||||
```Python
|
||||
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
|
||||
...
|
||||
```
|
||||
|
||||
しかし、Python は `johndoe` の最初の `j` と `stanleyjobson` の最初の `s` を比較した時点で、両者の文字列が同じでないと判断してすぐに `False` を返します。つまり「残りの文字を比較して計算資源を無駄にする必要はない」と考えるわけです。そしてアプリケーションは「ユーザー名またはパスワードが正しくありません」と返します。
|
||||
|
||||
次に、攻撃者がユーザー名 `stanleyjobsox`、パスワード `love123` で試すとします。
|
||||
|
||||
アプリケーションのコードは次のようになります:
|
||||
|
||||
```Python
|
||||
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
|
||||
...
|
||||
```
|
||||
|
||||
この場合、Python は `stanleyjobsox` と `stanleyjobson` の両方で `stanleyjobso` 全体を比較してから、文字列が同じでないと気づきます。したがって、「ユーザー名またはパスワードが正しくありません」と応答するまでに余分に数マイクロ秒かかります。
|
||||
|
||||
#### 応答時間が攻撃者を助ける { #the-time-to-answer-helps-the-attackers }
|
||||
|
||||
ここで、サーバーが「ユーザー名またはパスワードが正しくありません」というレスポンスを返すまでに、わずかに長い時間がかかったことに気づけば、攻撃者は何かしら正解に近づいた、すなわち先頭のいくつかの文字が正しかったことを知ることができます。
|
||||
|
||||
すると、`johndoe` よりも `stanleyjobsox` に近いものを狙って再試行できます。
|
||||
|
||||
#### 「プロ」レベルの攻撃 { #a-professional-attack }
|
||||
|
||||
もちろん、攻撃者はこれらを手作業では行わず、プログラムを書いて、1 秒間に数千〜数百万回のテストを行うでしょう。そして 1 回に 1 文字ずつ正しい文字を見つけていきます。
|
||||
|
||||
そうすることで、数分から数時間のうちに、攻撃者は私たちのアプリケーションの「助け」(応答にかかった時間)だけを利用して、正しいユーザー名とパスワードを推測できてしまいます。
|
||||
|
||||
#### `secrets.compare_digest()` で対策 { #fix-it-with-secrets-compare-digest }
|
||||
|
||||
しかし、私たちのコードでは実際に `secrets.compare_digest()` を使用しています。
|
||||
|
||||
要するに、`stanleyjobsox` と `stanleyjobson` を比較するのにかかる時間は、`johndoe` と `stanleyjobson` を比較するのにかかる時間と同じになります。パスワードでも同様です。
|
||||
|
||||
このように、アプリケーションコードで `secrets.compare_digest()` を使うと、この種の一連のセキュリティ攻撃に対して安全になります。
|
||||
|
||||
### エラーを返す { #return-the-error }
|
||||
|
||||
認証情報が不正であることを検出したら、ステータスコード 401(認証情報が提供されない場合と同じ)で `HTTPException` を返し、ブラウザに再度ログインプロンプトを表示させるためにヘッダー `WWW-Authenticate` を追加します:
|
||||
|
||||
{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# 高度なセキュリティ { #advanced-security }
|
||||
|
||||
## 追加機能 { #additional-features }
|
||||
|
||||
[チュートリアル - ユーザーガイド: セキュリティ](../../tutorial/security/index.md){.internal-link target=_blank}で扱ったもの以外にも、セキュリティを扱うための追加機能がいくつかあります。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
次の節は必ずしも「高度」ではありません。
|
||||
|
||||
あなたのユースケースでは、その中のいずれかに解決策があるかもしれません。
|
||||
|
||||
///
|
||||
|
||||
## まずチュートリアルを読む { #read-the-tutorial-first }
|
||||
|
||||
以下の節は、すでにメインの[チュートリアル - ユーザーガイド: セキュリティ](../../tutorial/security/index.md){.internal-link target=_blank}を読んでいることを前提とします。
|
||||
|
||||
いずれも同じ概念に基づいていますが、いくつかの追加機能を利用できます。
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
# OAuth2 のスコープ { #oauth2-scopes }
|
||||
|
||||
OAuth2 のスコープは **FastAPI** で直接利用でき、シームレスに統合されています。
|
||||
|
||||
これにより、OAuth2 標準に従った、よりきめ細かな権限システムを、OpenAPI 対応アプリケーション(および API ドキュメント)に統合できます。
|
||||
|
||||
スコープ付きの OAuth2 は、Facebook、Google、GitHub、Microsoft、X (Twitter) など、多くの大手認証プロバイダで使われている仕組みです。ユーザーやアプリケーションに特定の権限を付与するために利用されます。
|
||||
|
||||
「Facebook でログイン」「Google でログイン」「GitHub でログイン」「Microsoft でログイン」「X (Twitter) でログイン」するたびに、そのアプリケーションはスコープ付きの OAuth2 を使っています。
|
||||
|
||||
この節では、同じスコープ付き OAuth2 を使って、**FastAPI** アプリケーションで認証と認可を管理する方法を見ていきます。
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
これはやや高度な内容です。はじめたばかりであれば読み飛ばしても構いません。
|
||||
|
||||
OAuth2 のスコープは必ずしも必要ではなく、認証と認可は好きなやり方で実装できます。
|
||||
|
||||
ただし、スコープ付きの OAuth2 は、API(OpenAPI)や API ドキュメントにきれいに統合できます。
|
||||
|
||||
とはいえ、これらのスコープやその他のセキュリティ/認可要件の適用は、必要に応じてコードの中で行う必要があります。
|
||||
|
||||
多くの場合、スコープ付き OAuth2 はオーバースペックになりえます。
|
||||
|
||||
それでも必要だと分かっている場合や、興味がある場合は、このまま読み進めてください。
|
||||
|
||||
///
|
||||
|
||||
## OAuth2 のスコープと OpenAPI { #oauth2-scopes-and-openapi }
|
||||
|
||||
OAuth2 仕様では、「スコープ」は空白で区切られた文字列の一覧として定義されています。
|
||||
|
||||
各文字列の内容は任意ですが、空白は含められません。
|
||||
|
||||
これらのスコープは「権限」を表します。
|
||||
|
||||
OpenAPI(例: API ドキュメント)では、「セキュリティスキーム」を定義できます。
|
||||
|
||||
これらのセキュリティスキームの一つが OAuth2 を使う場合、スコープを宣言して利用できます。
|
||||
|
||||
各「スコープ」は、ただの文字列(空白なし)です。
|
||||
|
||||
通常、特定のセキュリティ権限を宣言するために使われます。例えば:
|
||||
|
||||
- `users:read` や `users:write` は一般的な例です。
|
||||
- `instagram_basic` は Facebook / Instagram で使われています。
|
||||
- `https://www.googleapis.com/auth/drive` は Google で使われています。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
OAuth2 において「スコープ」は、必要な特定の権限を宣言する単なる文字列です。
|
||||
|
||||
`:` のような他の文字が含まれていても、URL であっても問題ありません。
|
||||
|
||||
それらの詳細は実装依存です。
|
||||
|
||||
OAuth2 にとっては、単に文字列に過ぎません。
|
||||
|
||||
///
|
||||
|
||||
## 全体像 { #global-view }
|
||||
|
||||
まず、メインの**チュートリアル - ユーザーガイド**にある [OAuth2(パスワード[ハッシュ化あり])、Bearer と JWT トークン](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} の例から変更される部分を、スコープ付き OAuth2 を使って手早く見てみましょう。
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
|
||||
|
||||
では、これらの変更を一つずつ確認していきます。
|
||||
|
||||
## OAuth2 のセキュリティスキーム { #oauth2-security-scheme }
|
||||
|
||||
最初の変更点は、`me` と `items` の 2 つのスコープを持つ OAuth2 セキュリティスキームを宣言していることです。
|
||||
|
||||
`scopes` パラメータは、各スコープをキー、その説明を値とする `dict` を受け取ります:
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
|
||||
|
||||
これらのスコープを宣言しているため、ログイン/認可時に API ドキュメントに表示されます。
|
||||
|
||||
そして、付与するスコープ(`me`、`items`)を選択できます。
|
||||
|
||||
これは、Facebook、Google、GitHub などでログイン時に権限を付与する際と同じ仕組みです:
|
||||
|
||||
<img src="/img/tutorial/security/image11.png">
|
||||
|
||||
## スコープ付きの JWT トークン { #jwt-token-with-scopes }
|
||||
|
||||
次に、トークンの path operation を修正して、要求されたスコープを返すようにします。
|
||||
|
||||
引き続き同じ `OAuth2PasswordRequestForm` を使用します。これには、リクエストで受け取った各スコープを含む、`str` の `list` である `scopes` プロパティが含まれます。
|
||||
|
||||
そして、そのスコープを JWT トークンの一部として返します。
|
||||
|
||||
/// danger | 警告
|
||||
|
||||
簡単のため、ここでは受け取ったスコープをそのままトークンに追加しています。
|
||||
|
||||
しかし、本番アプリケーションではセキュリティのため、ユーザーが実際に持つことができるスコープ、または事前に定義したスコープだけを追加するようにしてください。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
|
||||
|
||||
## path operation と依存関係でスコープを宣言 { #declare-scopes-in-path-operations-and-dependencies }
|
||||
|
||||
ここでは、`/users/me/items/` の path operation が `items` スコープを必要とするように宣言します。
|
||||
|
||||
そのために、`fastapi` から `Security` をインポートして使います。
|
||||
|
||||
`Security` は(`Depends` と同様に)依存関係を宣言できますが、さらにスコープ(文字列)のリストを受け取る `scopes` パラメータも持ちます。
|
||||
|
||||
この場合、`Security` に依存関数 `get_current_active_user` を渡します(`Depends` と同様です)。
|
||||
|
||||
加えて、`items` という 1 つのスコープ(複数でも可)を含む `list` も渡します。
|
||||
|
||||
依存関数 `get_current_active_user` は、`Depends` だけでなく `Security` でもサブ依存関係を宣言できます。自身のサブ依存関数(`get_current_user`)を宣言し、さらにスコープ要件を追加します。
|
||||
|
||||
この場合、`me` スコープを要求します(複数のスコープも可)。
|
||||
|
||||
/// note | 備考
|
||||
|
||||
異なる場所で異なるスコープを追加する必要は必ずしもありません。
|
||||
|
||||
ここでは、**FastAPI** が異なるレベルで宣言されたスコープをどのように扱うかを示すためにそうしています。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
|
||||
|
||||
/// info | 技術詳細
|
||||
|
||||
`Security` は実際には `Depends` のサブクラスで、後述する追加パラメータが 1 つあるだけです。
|
||||
|
||||
しかし `Depends` の代わりに `Security` を使うことで、**FastAPI** はセキュリティスコープを宣言・内部利用でき、OpenAPI で API をドキュメント化できると判断します。
|
||||
|
||||
なお、`fastapi` から `Query`、`Path`、`Depends`、`Security` などをインポートする際、それらは実際には特殊なクラスを返す関数です。
|
||||
|
||||
///
|
||||
|
||||
## `SecurityScopes` を使う { #use-securityscopes }
|
||||
|
||||
次に、依存関数 `get_current_user` を更新します。
|
||||
|
||||
これは上記の依存関係から使用されます。
|
||||
|
||||
ここで、先ほど作成した同じ OAuth2 スキームを依存関係(`oauth2_scheme`)として宣言して使います。
|
||||
|
||||
この依存関数自体はスコープ要件を持たないため、`oauth2_scheme` には `Depends` を使えます。セキュリティスコープを指定する必要がない場合は `Security` を使う必要はありません。
|
||||
|
||||
さらに、`fastapi.security` からインポートする特別な型 `SecurityScopes` のパラメータを宣言します。
|
||||
|
||||
この `SecurityScopes` クラスは `Request` に似ています(`Request` はリクエストオブジェクトを直接取得するために使いました)。
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
|
||||
|
||||
## `scopes` を使う { #use-the-scopes }
|
||||
|
||||
パラメータ `security_scopes` は `SecurityScopes` 型になります。
|
||||
|
||||
このオブジェクトは、自身およびこれをサブ依存として使うすべての依存関係で要求されるスコープを含む `scopes` プロパティ(リスト)を持ちます。つまり、すべての「依存元」... 少し分かりにくいかもしれませんが、後で再度説明します。
|
||||
|
||||
`security_scopes`(`SecurityScopes` クラスのインスタンス)は、要求されたスコープを空白で連結した 1 つの文字列を返す `scope_str` も提供します(これを使います)。
|
||||
|
||||
後で複数箇所で再利用(raise)できるように、`HTTPException` を 1 つ作成します。
|
||||
|
||||
この例外には、要求されたスコープがあればそれらを空白区切りの文字列(`scope_str` を使用)として含めます。このスコープ文字列は `WWW-Authenticate` ヘッダに入れます(仕様の一部です)。
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
|
||||
|
||||
## `username` とデータ構造の検証 { #verify-the-username-and-data-shape }
|
||||
|
||||
`username` を取得できていることを確認し、スコープを取り出します。
|
||||
|
||||
そして、そのデータを Pydantic モデルで検証します(`ValidationError` 例外を捕捉)。JWT トークンの読み取りや Pydantic によるデータ検証でエラーが発生した場合は、先ほど作成した `HTTPException` を送出します。
|
||||
|
||||
そのために、Pydantic モデル `TokenData` に新しいプロパティ `scopes` を追加します。
|
||||
|
||||
Pydantic でデータを検証することで、例えばスコープは `str` の `list`、`username` は `str` といった、正確な型になっていることを保証できます。
|
||||
|
||||
そうしておけば、例えば誤って `dict` などが入って後でアプリケーションを破壊してしまい、セキュリティリスクになる、といった事態を避けられます。
|
||||
|
||||
また、その `username` を持つユーザーが存在することも確認し、存在しなければ、やはり先ほどの例外を送出します。
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
|
||||
|
||||
## `scopes` の検証 { #verify-the-scopes }
|
||||
|
||||
この依存関数およびすべての依存元(path operation を含む)が要求するすべてのスコープが、受け取ったトークンに含まれていることを検証し、含まれていなければ `HTTPException` を送出します。
|
||||
|
||||
そのために、これらすべてのスコープを `str` の `list` として含む `security_scopes.scopes` を使います。
|
||||
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
|
||||
|
||||
## 依存関係ツリーとスコープ { #dependency-tree-and-scopes }
|
||||
|
||||
依存関係ツリーとスコープをもう一度見てみましょう。
|
||||
|
||||
`get_current_active_user` 依存関係は `get_current_user` をサブ依存として持つため、`get_current_active_user` で宣言された `"me"` スコープは、`get_current_user` に渡される `security_scopes.scopes` の必須スコープ一覧に含まれます。
|
||||
|
||||
path operation 自体も `"items"` スコープを宣言するため、これも `get_current_user` に渡される `security_scopes.scopes` に含まれます。
|
||||
|
||||
依存関係とスコープの階層は次のようになります:
|
||||
|
||||
- *path operation* `read_own_items` には:
|
||||
- 依存関係に対して必須スコープ `["items"]` がある:
|
||||
- `get_current_active_user`:
|
||||
- 依存関数 `get_current_active_user` には:
|
||||
- 依存関係に対して必須スコープ `["me"]` がある:
|
||||
- `get_current_user`:
|
||||
- 依存関数 `get_current_user` には:
|
||||
- 自身に必須スコープはない。
|
||||
- `oauth2_scheme` を使う依存関係がある。
|
||||
- `SecurityScopes` 型の `security_scopes` パラメータがある:
|
||||
- この `security_scopes` パラメータは、上で宣言されたすべてのスコープを含む `list` を持つ `scopes` プロパティを持つ。したがって:
|
||||
- *path operation* `read_own_items` では、`security_scopes.scopes` は `["me", "items"]` を含む。
|
||||
- *path operation* `read_users_me` では、`security_scopes.scopes` は `["me"]` を含む。これは依存関係 `get_current_active_user` に宣言されているため。
|
||||
- *path operation* `read_system_status` では、`security_scopes.scopes` は `[]`(空)になる。`scopes` を持つ `Security` を宣言しておらず、その依存関係 `get_current_user` も `scopes` を宣言していないため。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
重要で「魔法のよう」な点は、`get_current_user` が path operation ごとに異なる `scopes` のリストをチェックすることになる、ということです。
|
||||
|
||||
それは、それぞれの path operation と、その path operation の依存関係ツリー内の各依存関係で宣言された `scopes` によって決まります。
|
||||
|
||||
///
|
||||
|
||||
## `SecurityScopes` の詳細 { #more-details-about-securityscopes }
|
||||
|
||||
`SecurityScopes` はどの地点でも、複数箇所でも使えます。「ルート」の依存関係である必要はありません。
|
||||
|
||||
常に、その時点の `Security` 依存関係と、**その特定の** path operation と **その特定の** 依存関係ツリーにおける、すべての依存元で宣言されたセキュリティスコープを持ちます。
|
||||
|
||||
`SecurityScopes` には依存元で宣言されたすべてのスコープが入るため、トークンが必要なスコープを持っているかどうかを中央の依存関数で検証し、path operation ごとに異なるスコープ要件を宣言する、といった使い方ができます。
|
||||
|
||||
これらは path operation ごとに独立して検証されます。
|
||||
|
||||
## チェック { #check-it }
|
||||
|
||||
API ドキュメントを開くと、認証して、許可するスコープを指定できます。
|
||||
|
||||
<img src="/img/tutorial/security/image11.png">
|
||||
|
||||
どのスコープも選択しない場合は「認証済み」にはなりますが、`/users/me/` や `/users/me/items/` にアクセスしようとすると、権限が不足しているというエラーになります。`/status/` には引き続きアクセスできます。
|
||||
|
||||
`me` スコープだけを選択し、`items` スコープを選択しない場合は、`/users/me/` にはアクセスできますが、`/users/me/items/` にはアクセスできません。
|
||||
|
||||
これは、ユーザーがアプリケーションに与えた権限の範囲に応じて、サードパーティアプリケーションがこれらの path operation のいずれかに、ユーザーから提供されたトークンでアクセスしようとしたときに起こる動作です。
|
||||
|
||||
## サードパーティ統合について { #about-third-party-integrations }
|
||||
|
||||
この例では、OAuth2 の「password」フローを使用しています。
|
||||
|
||||
これは、(おそらく自前のフロントエンドで)自分たちのアプリケーションにログインする場合に適しています。
|
||||
|
||||
自分たちで管理しているため、`username` と `password` を受け取る相手を信頼できるからです。
|
||||
|
||||
しかし、他者が接続する OAuth2 アプリケーション(Facebook、Google、GitHub などに相当する認証プロバイダ)を構築する場合は、他のいずれかのフローを使用すべきです。
|
||||
|
||||
最も一般的なのは implicit フローです。
|
||||
|
||||
最も安全なのは code フローですが、手順が多く実装がより複雑です。複雑なため、多くのプロバイダは結局 implicit フローを推奨することがあります。
|
||||
|
||||
/// note | 備考
|
||||
|
||||
各認証プロバイダがフローに独自の名称を付け、自社のブランドの一部にするのは一般的です。
|
||||
|
||||
しかし、最終的には同じ OAuth2 標準を実装しています。
|
||||
|
||||
///
|
||||
|
||||
**FastAPI** には、これらすべての OAuth2 認証フロー向けのユーティリティが `fastapi.security.oauth2` に含まれています。
|
||||
|
||||
## デコレータ `dependencies` での `Security` { #security-in-decorator-dependencies }
|
||||
|
||||
デコレータの `dependencies` パラメータに `Depends` の `list` を定義できるのと同様([path operation デコレータでの依存関係](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 参照)、ここで `scopes` を指定した `Security` も使用できます。
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
# 設定と環境変数 { #settings-and-environment-variables }
|
||||
|
||||
多くの場合、アプリケーションは外部の設定や構成を必要とします。たとえば、シークレットキー、データベース認証情報、メールサービスの認証情報などです。
|
||||
|
||||
これらの設定の多くは可変(変更されうる)で、データベースのURLのようなものがあります。また、多くはシークレットのように機微な情報です。
|
||||
|
||||
そのため、アプリケーションが読み取る環境変数で提供するのが一般的です。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
環境変数について理解するには、[環境変数](../environment-variables.md){.internal-link target=_blank}を参照してください。
|
||||
|
||||
///
|
||||
|
||||
## 型とバリデーション { #types-and-validation }
|
||||
|
||||
これらの環境変数は Python の外部にあり、他のプログラムやシステム全体(Linux、Windows、macOS といった異なるOSを含む)と互換性が必要なため、文字列テキストのみを扱えます。
|
||||
|
||||
つまり、Python で環境変数から読み取られる値はすべて `str` になり、他の型への変換やバリデーションはコードで行う必要があります。
|
||||
|
||||
## Pydantic の `Settings` { #pydantic-settings }
|
||||
|
||||
幸いなことに、Pydantic には環境変数から来る設定を扱うための優れたユーティリティがあり、<a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a> で提供されています。
|
||||
|
||||
### `pydantic-settings` のインストール { #install-pydantic-settings }
|
||||
|
||||
まず、[仮想環境](../virtual-environments.md){.internal-link target=_blank}を作成して有効化し、`pydantic-settings` パッケージをインストールします:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install pydantic-settings
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
また、次のように `all` エクストラをインストールすると付属します:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install "fastapi[all]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### `Settings` オブジェクトを作成 { #create-the-settings-object }
|
||||
|
||||
Pydantic から `BaseSettings` をインポートして、そのサブクラスを作成します。これは Pydantic モデルとほぼ同じです。
|
||||
|
||||
Pydantic モデルと同様に、型アノテーションと(必要なら)デフォルト値を持つクラス属性を宣言します。
|
||||
|
||||
`Field()` による追加バリデーションなど、Pydantic モデルで使えるのと同じバリデーション機能をすべて利用できます。
|
||||
|
||||
{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
コピペ用に手早く使いたい場合は、この例ではなく、下の最後の例を使ってください。
|
||||
|
||||
///
|
||||
|
||||
その後、その `Settings` クラスのインスタンス(この例では `settings` オブジェクト)を作成すると、Pydantic は環境変数を大文字小文字を区別せずに読み取ります。つまり、大文字の `APP_NAME` という変数は、属性 `app_name` に対しても読み取られます。
|
||||
|
||||
次に、データが変換・バリデーションされます。したがって、`settings` オブジェクトを使うと、宣言した型のデータ(例: `items_per_user` は `int`)が得られます。
|
||||
|
||||
### `settings` の使用 { #use-the-settings }
|
||||
|
||||
次に、アプリケーションで新しい `settings` オブジェクトを使用できます:
|
||||
|
||||
{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *}
|
||||
|
||||
### サーバーを実行 { #run-the-server }
|
||||
|
||||
次に、設定を環境変数として渡してサーバーを実行します。たとえば、`ADMIN_EMAIL` と `APP_NAME` を次のように設定できます:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
1つのコマンドに複数の環境変数を設定するには、スペースで区切ってコマンドの前に並べます。
|
||||
|
||||
///
|
||||
|
||||
すると、`admin_email` の設定は `"deadpool@example.com"` に設定されます。
|
||||
|
||||
`app_name` は `"ChimichangApp"` になります。
|
||||
|
||||
`items_per_user` はデフォルト値の `50` のままです。
|
||||
|
||||
## 別モジュールでの設定 { #settings-in-another-module }
|
||||
|
||||
[大規模アプリケーション - 複数ファイル](../tutorial/bigger-applications.md){.internal-link target=_blank} で見たように、これらの設定を別のモジュールファイルに置くこともできます。
|
||||
|
||||
たとえば、`config.py` というファイルに次のように書けます:
|
||||
|
||||
{* ../../docs_src/settings/app01_py310/config.py *}
|
||||
|
||||
そして、`main.py` というファイルでそれを使います:
|
||||
|
||||
{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
[大規模アプリケーション - 複数ファイル](../tutorial/bigger-applications.md){.internal-link target=_blank} で見たように、`__init__.py` ファイルも必要です。
|
||||
|
||||
///
|
||||
|
||||
## 依存関係での設定 { #settings-in-a-dependency }
|
||||
|
||||
場合によっては、どこでも使うグローバルな `settings` オブジェクトを持つ代わりに、依存関係から設定を提供すると便利なことがあります。
|
||||
|
||||
これは特にテスト時に有用で、依存関係を独自のカスタム設定で簡単にオーバーライドできるからです。
|
||||
|
||||
### 設定ファイル { #the-config-file }
|
||||
|
||||
前の例から続けると、`config.py` ファイルは次のようになります:
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *}
|
||||
|
||||
ここでは、デフォルトのインスタンス `settings = Settings()` を作成していないことに注意してください。
|
||||
|
||||
### メインアプリファイル { #the-main-app-file }
|
||||
|
||||
ここでは、新しい `config.Settings()` を返す依存関係を作成します。
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`@lru_cache` については後で説明します。
|
||||
|
||||
今は `get_settings()` が普通の関数だと考えてください。
|
||||
|
||||
///
|
||||
|
||||
そして、*path operation 関数*から依存関係として要求し、必要な場所でどこでも使えます。
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *}
|
||||
|
||||
### 設定とテスト { #settings-and-testing }
|
||||
|
||||
次に、`get_settings` の依存関係オーバーライドを作ることで、テスト中に別の設定オブジェクトを提供するのがとても簡単になります:
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *}
|
||||
|
||||
依存関係オーバーライドでは、新しい `Settings` オブジェクトを作る際に `admin_email` に新しい値を設定し、その新しいオブジェクトを返します。
|
||||
|
||||
そして、それが使用されていることをテストできます。
|
||||
|
||||
## `.env` ファイルの読み込み { #reading-a-env-file }
|
||||
|
||||
変更が多くなりそうな設定が多数ある場合、環境ごとにファイルに入れて、環境変数としてそこから読み込むと便利なことがあります。
|
||||
|
||||
このプラクティスは十分に一般的で名前もあり、これらの環境変数は通常 `.env` というファイルに置かれ、そのファイルは「dotenv」と呼ばれます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
ドット(`.`)で始まるファイルは、Linux や macOS のような Unix 系システムでは隠しファイルです。
|
||||
|
||||
ただし、dotenv ファイルは必ずしもその正確なファイル名である必要はありません。
|
||||
|
||||
///
|
||||
|
||||
Pydantic は外部ライブラリを使ってこの種のファイルからの読み込みをサポートしています。詳細は <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a> を参照してください。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
これを機能させるには、`pip install python-dotenv` が必要です。
|
||||
|
||||
///
|
||||
|
||||
### `.env` ファイル { #the-env-file }
|
||||
|
||||
次のような `.env` ファイルを用意できます:
|
||||
|
||||
```bash
|
||||
ADMIN_EMAIL="deadpool@example.com"
|
||||
APP_NAME="ChimichangApp"
|
||||
```
|
||||
|
||||
### `.env` から設定を読む { #read-settings-from-env }
|
||||
|
||||
そして、`config.py` を次のように更新します:
|
||||
|
||||
{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`model_config` 属性は Pydantic の設定専用です。詳しくは <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a> を参照してください。
|
||||
|
||||
///
|
||||
|
||||
ここでは、Pydantic の `Settings` クラス内で設定 `env_file` を定義し、使用したい dotenv ファイルのファイル名を指定しています。
|
||||
|
||||
### `lru_cache` で `Settings` を一度だけ作成 { #creating-the-settings-only-once-with-lru-cache }
|
||||
|
||||
ディスクからファイルを読むのは通常コスト(遅延)が高い処理なので、1回だけ実行して同じ設定オブジェクトを再利用し、各リクエストごとに読み直さないのが望ましいです。
|
||||
|
||||
しかし、次のようにするたびに:
|
||||
|
||||
```Python
|
||||
Settings()
|
||||
```
|
||||
|
||||
新しい `Settings` オブジェクトが作成され、その作成時に `.env` ファイルが再度読み込まれます。
|
||||
|
||||
依存関数が次のようであれば:
|
||||
|
||||
```Python
|
||||
def get_settings():
|
||||
return Settings()
|
||||
```
|
||||
|
||||
各リクエストごとにそのオブジェクトを作成し、各リクエストごとに `.env` ファイルを読み込むことになります。⚠️
|
||||
|
||||
しかし、上に `@lru_cache` デコレータを使っているので、`Settings` オブジェクトは最初に呼び出されたときに一度だけ作成されます。✔️
|
||||
|
||||
{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *}
|
||||
|
||||
その後のリクエスト用の依存関係で `get_settings()` が呼ばれるたびに、`get_settings()` の内部コードを実行して新しい `Settings` オブジェクトを作るのではなく、最初の呼び出しで返されたのと同じオブジェクトを何度でも返します。
|
||||
|
||||
#### `lru_cache` の技術詳細 { #lru-cache-technical-details }
|
||||
|
||||
`@lru_cache` は、毎回関数のコードを実行して再計算するのではなく、最初に返した値を返すように、修飾する関数を変更します。
|
||||
|
||||
したがって、その下の関数は引数の組み合わせごとに一度だけ実行されます。そして、その各引数の組み合わせで返された値は、まったく同じ引数の組み合わせで呼び出されたときに何度でも再利用されます。
|
||||
|
||||
たとえば、次のような関数があるとします:
|
||||
|
||||
```Python
|
||||
@lru_cache
|
||||
def say_hi(name: str, salutation: str = "Ms."):
|
||||
return f"Hello {salutation} {name}"
|
||||
```
|
||||
|
||||
プログラムは次のように実行されます:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
participant code as Code
|
||||
participant function as say_hi()
|
||||
participant execute as Execute function
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Rick")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 0, .1)
|
||||
code ->> function: say_hi(name="Rick", salutation="Mr.")
|
||||
function ->> execute: execute function code
|
||||
execute ->> code: return the result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Rick")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
|
||||
rect rgba(0, 255, 255, .1)
|
||||
code ->> function: say_hi(name="Camila")
|
||||
function ->> code: return stored result
|
||||
end
|
||||
```
|
||||
|
||||
今回の依存関数 `get_settings()` の場合、関数は引数を一切取りません。そのため、常に同じ値を返します。
|
||||
|
||||
この方法は、ほとんどグローバル変数のように振る舞います。しかし、依存関数を使っているので、テストのために簡単にオーバーライドできます。
|
||||
|
||||
`@lru_cache` は Python 標準ライブラリの `functools` の一部です。詳細は <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">Python の `@lru_cache` ドキュメント</a>を参照してください。
|
||||
|
||||
## まとめ { #recap }
|
||||
|
||||
Pydantic Settings を使うことで、アプリケーションの設定や構成を、Pydantic モデルの力を活かして扱えます。
|
||||
|
||||
* 依存関係を使うことで、テストを簡素化できます。
|
||||
* `.env` ファイルを利用できます。
|
||||
* `@lru_cache` を使うと、各リクエストごとに dotenv ファイルを繰り返し読み込むのを避けつつ、テスト時にはオーバーライドできます。
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
# サブアプリケーション - マウント { #sub-applications-mounts }
|
||||
|
||||
それぞれ独立した OpenAPI とドキュメント UI を持つ2つの独立した FastAPI アプリケーションが必要な場合、メインアプリに1つ(以上)のサブアプリケーションを「マウント」できます。
|
||||
|
||||
## FastAPI アプリケーションのマウント { #mounting-a-fastapi-application }
|
||||
|
||||
「マウント」とは、特定のパスに完全に「独立した」アプリケーションを追加し、そのサブアプリケーションで宣言された path operation によって、そのパス以下のすべてを処理させることを意味します。
|
||||
|
||||
### トップレベルアプリケーション { #top-level-application }
|
||||
|
||||
まず、メインのトップレベル **FastAPI** アプリケーションと、その path operation を作成します:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *}
|
||||
|
||||
### サブアプリケーション { #sub-application }
|
||||
|
||||
次に、サブアプリケーションとその path operation を作成します。
|
||||
|
||||
このサブアプリケーションは通常の FastAPI アプリケーションですが、これを「マウント」します:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *}
|
||||
|
||||
### サブアプリケーションをマウント { #mount-the-sub-application }
|
||||
|
||||
トップレベルのアプリケーション `app` に、サブアプリケーション `subapi` をマウントします。
|
||||
|
||||
この例では、パス `/subapi` にマウントされます:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *}
|
||||
|
||||
### 自動 API ドキュメントの確認 { #check-the-automatic-api-docs }
|
||||
|
||||
では、`fastapi` コマンドでこのファイルを実行します:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
そして、<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> を開きます。
|
||||
|
||||
メインアプリ用の自動 API ドキュメントが表示され、そのアプリ自身の path operation のみが含まれます:
|
||||
|
||||
<img src="/img/tutorial/sub-applications/image01.png">
|
||||
|
||||
次に、サブアプリケーションのドキュメント <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a> を開きます。
|
||||
|
||||
サブアプリケーション用の自動 API ドキュメントが表示され、そのアプリ自身の path operation のみが、正しいサブパス接頭辞 `/subapi` の下で表示されます:
|
||||
|
||||
<img src="/img/tutorial/sub-applications/image02.png">
|
||||
|
||||
どちらの UI でも操作すれば正しく動作します。ブラウザがそれぞれのアプリ/サブアプリと通信できるためです。
|
||||
|
||||
### 技術詳細: `root_path` { #technical-details-root-path }
|
||||
|
||||
上記のようにサブアプリケーションをマウントすると、FastAPI は ASGI 仕様の `root_path` と呼ばれる仕組みを使って、そのサブアプリケーションへのマウントパスを伝播します。
|
||||
|
||||
このため、サブアプリケーションはドキュメント UI でそのパス接頭辞を使用すべきことを認識できます。
|
||||
|
||||
さらに、サブアプリケーション自身が別のサブアプリケーションをマウントしていても問題ありません。FastAPI がこれらの `root_path` をすべて自動的に処理するためです。
|
||||
|
||||
`root_path` の詳細や明示的な指定方法については、[プロキシの背後で](behind-a-proxy.md){.internal-link target=_blank} の節で学べます。
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
# テンプレート { #templates }
|
||||
|
||||
**FastAPI** では任意のテンプレートエンジンを使用できます。
|
||||
|
||||
Flask などでも使われている Jinja2 が一般的な選択肢です。
|
||||
|
||||
Starlette によって提供され、**FastAPI** アプリで直接使える、簡単に設定できるユーティリティがあります。
|
||||
|
||||
## 依存関係のインストール { #install-dependencies }
|
||||
|
||||
[仮想環境](../virtual-environments.md){.internal-link target=_blank} を作成して有効化し、`jinja2` をインストールします:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install jinja2
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## `Jinja2Templates` の使用 { #using-jinja2templates }
|
||||
|
||||
* `Jinja2Templates` をインポートします。
|
||||
* 後で再利用できる `templates` オブジェクトを作成します。
|
||||
* テンプレートを返す path operation に `Request` パラメータを宣言します。
|
||||
* 作成した `templates` を使って `TemplateResponse` をレンダリングして返します。テンプレート名、リクエストオブジェクト、Jinja2 テンプレート内で使用するキーと値のペアからなる "context" の辞書を渡します。
|
||||
|
||||
{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *}
|
||||
|
||||
/// note | 備考
|
||||
|
||||
FastAPI 0.108.0、Starlette 0.29.0 以前では、`name` は最初のパラメータでした。
|
||||
|
||||
またそれ以前のバージョンでは、`request` オブジェクトは Jinja2 用のコンテキスト内のキーと値のペアの一部として渡されていました。
|
||||
|
||||
///
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`response_class=HTMLResponse` を宣言すると、ドキュメント UI がレスポンスが HTML であることを認識できます。
|
||||
|
||||
///
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
`from starlette.templating import Jinja2Templates` を使うこともできます。
|
||||
|
||||
**FastAPI** は、開発者であるあなたの利便性のために、`starlette.templating` と同じものを `fastapi.templating` として提供しています。しかし、利用可能なレスポンスのほとんどは Starlette から直接提供されています。`Request` や `StaticFiles` も同様です。
|
||||
|
||||
///
|
||||
|
||||
## テンプレートの作成 { #writing-templates }
|
||||
|
||||
例えば、`templates/item.html` に次のようなテンプレートを書きます:
|
||||
|
||||
```jinja hl_lines="7"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
### テンプレートのコンテキスト値 { #template-context-values }
|
||||
|
||||
次のような HTML 内で:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja
|
||||
Item ID: {{ id }}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...渡した "context" の `dict` から取得した `id` が表示されます:
|
||||
|
||||
```Python
|
||||
{"id": id}
|
||||
```
|
||||
|
||||
例えば、ID が `42` の場合は次のようにレンダリングされます:
|
||||
|
||||
```html
|
||||
Item ID: 42
|
||||
```
|
||||
|
||||
### テンプレートの `url_for` の引数 { #template-url-for-arguments }
|
||||
|
||||
テンプレート内でも `url_for()` を使用できます。引数には、対応する path operation 関数で使われるのと同じ引数を取ります。
|
||||
|
||||
したがって、次の部分は:
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jinja
|
||||
<a href="{{ url_for('read_item', id=id) }}">
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...path operation 関数 `read_item(id=id)` が処理するのと同じ URL へのリンクを生成します。
|
||||
|
||||
例えば、ID が `42` の場合は次のようにレンダリングされます:
|
||||
|
||||
```html
|
||||
<a href="/items/42">
|
||||
```
|
||||
|
||||
## テンプレートと静的ファイル { #templates-and-static-files }
|
||||
|
||||
テンプレート内で `url_for()` を使用し、例えば `name="static"` でマウントした `StaticFiles` に対して利用できます。
|
||||
|
||||
```jinja hl_lines="4"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
この例では、`static/styles.css` の CSS ファイルにリンクします:
|
||||
|
||||
```CSS hl_lines="4"
|
||||
{!../../docs_src/templates/static/styles.css!}
|
||||
```
|
||||
|
||||
また、`StaticFiles` を使用しているため、その CSS ファイルは **FastAPI** アプリケーションから URL `/static/styles.css` で自動的に配信されます。
|
||||
|
||||
## さらに詳しく { #more-details }
|
||||
|
||||
より詳しい内容(テンプレートのテスト方法など)については、<a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">Starlette のテンプレートに関するドキュメント</a>を参照してください。
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# 依存関係のオーバーライドによるテスト { #testing-dependencies-with-overrides }
|
||||
|
||||
## テスト時の依存関係のオーバーライド { #overriding-dependencies-during-testing }
|
||||
|
||||
テスト中に依存関係をオーバーライドしたい場面がいくつかあります。
|
||||
|
||||
元の依存関係(およびそれにぶら下がるサブ依存関係)を実行したくない場合です。
|
||||
|
||||
代わりに、テストの間だけ(特定のテストだけでも)使われる別の依存関係を提供し、元の依存関係の値が使われていた箇所で利用できる値を返したいのです。
|
||||
|
||||
### ユースケース: 外部サービス { #use-cases-external-service }
|
||||
|
||||
例として、呼び出す必要がある外部の認証プロバイダがあるとします。
|
||||
|
||||
トークンを送ると、認証済みユーザーが返ってきます。
|
||||
|
||||
このプロバイダはリクエストごとに課金されるかもしれず、テスト用に固定のモックユーザーを使う場合に比べて呼び出しに余分な時間がかかるかもしれません。
|
||||
|
||||
外部プロバイダ自体の動作は一度はテストしたいでしょうが、実行されるすべてのテストで毎回呼び出す必要はありません。
|
||||
|
||||
この場合、そのプロバイダを呼び出す依存関係をオーバーライドし、テストのときだけモックユーザーを返すカスタムの依存関係を使えます。
|
||||
|
||||
### app.dependency_overrides 属性を使う { #use-the-app-dependency-overrides-attribute }
|
||||
|
||||
このような場合のために、**FastAPI** アプリケーションには `app.dependency_overrides` という属性があり、これは単純な `dict` です。
|
||||
|
||||
テスト用に依存関係をオーバーライドするには、キーに元の依存関係(関数)を、値にオーバーライドする依存関係(別の関数)を設定します。
|
||||
|
||||
すると **FastAPI** は元の依存関係の代わりにそのオーバーライドを呼び出します。
|
||||
|
||||
{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
アプリケーション内のどこで使われている依存関係に対しても、依存関係のオーバーライドを設定できます。
|
||||
|
||||
元の依存関係は、*path operation 関数*、*path operation デコレータ*(戻り値を使わない場合)、`.include_router()` の呼び出しなど、さまざまな場所で使われていてもかまいません。
|
||||
|
||||
FastAPI はそれでもオーバーライドできます。
|
||||
|
||||
///
|
||||
|
||||
その後、`app.dependency_overrides` を空の `dict` に設定することで、オーバーライドをリセット(削除)できます:
|
||||
|
||||
```Python
|
||||
app.dependency_overrides = {}
|
||||
```
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
一部のテストの間だけ依存関係をオーバーライドしたい場合は、テストの開始時(テスト関数内)にオーバーライドを設定し、終了時(テスト関数の末尾)にリセットするとよいです。
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# イベントのテスト: lifespan と startup - shutdown { #testing-events-lifespan-and-startup-shutdown }
|
||||
|
||||
テストで `lifespan` を実行する必要がある場合は、`with` 文と併用して `TestClient` を使用できます:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *}
|
||||
|
||||
より詳しい内容は、[公式 Starlette ドキュメントの「テストでの lifespan の実行」](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) を参照してください。
|
||||
|
||||
非推奨の `startup` および `shutdown` イベントについては、次のように `TestClient` を使用できます:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# WebSocket のテスト { #testing-websockets }
|
||||
|
||||
WebSocket をテストするのにも同じ `TestClient` を使用できます。
|
||||
|
||||
そのために、`with` 文の中で `TestClient` を使用し、WebSocket に接続します:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *}
|
||||
|
||||
/// note | 備考
|
||||
|
||||
詳細については、Starlette のドキュメント「<a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">WebSocket のテスト</a>」を参照してください。
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# Request を直接使う { #using-the-request-directly }
|
||||
|
||||
これまで、必要なリクエストの各部分を、その型とともに宣言してきました。
|
||||
|
||||
次の場所からデータを取得します:
|
||||
|
||||
- パスのパラメータ
|
||||
- ヘッダー
|
||||
- クッキー
|
||||
- など
|
||||
|
||||
こうすることで、**FastAPI** はそのデータを検証し、変換し、API のドキュメントを自動生成します。
|
||||
|
||||
しかし、`Request` オブジェクトに直接アクセスする必要がある場面もあります。
|
||||
|
||||
## `Request` オブジェクトの詳細 { #details-about-the-request-object }
|
||||
|
||||
**FastAPI** は内部的には **Starlette** の上にいくつかのツール層を載せたものなので、必要に応じて Starlette の <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> オブジェクトを直接使えます。
|
||||
|
||||
また、`Request` オブジェクトから直接データ(例: ボディ)を取得する場合、そのデータは FastAPI によって検証・変換・ドキュメント化(OpenAPI による自動 API ユーザーインターフェース向け)されません。
|
||||
|
||||
ただし、通常どおりに宣言された他のパラメータ(例: Pydantic モデルのボディ)は引き続き検証・変換・注釈付けなどが行われます。
|
||||
|
||||
それでも、`Request` オブジェクトを取得するのが有用な特定のケースがあります。
|
||||
|
||||
## `Request` オブジェクトを直接使う { #use-the-request-object-directly }
|
||||
|
||||
たとえば、path operation 関数内でクライアントの IP アドレス/ホストを取得したいとします。
|
||||
|
||||
そのためには、リクエストに直接アクセスする必要があります。
|
||||
|
||||
{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *}
|
||||
|
||||
path operation 関数の引数として `Request` 型のパラメータを宣言すると、**FastAPI** はその引数に `Request` を渡します。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
この例では、`Request` 型の引数に加えて、パスパラメータも宣言しています。
|
||||
|
||||
そのため、パスパラメータは取り出され、検証され、指定した型に変換され、OpenAPI で注釈(ドキュメント化)されます。
|
||||
|
||||
同様に、通常どおり任意の他のパラメータを宣言しつつ、追加で `Request` も受け取れます。
|
||||
|
||||
///
|
||||
|
||||
## `Request` のドキュメント { #request-documentation }
|
||||
|
||||
より詳しくは、<a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">公式 Starlette ドキュメントサイトの `Request` オブジェクト</a>を参照してください。
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
`from starlette.requests import Request` を使うこともできます。
|
||||
|
||||
**FastAPI** は開発者である皆さんの便宜のために直接提供していますが、これは Starlette からそのまま提供されているものです。
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# WSGI の組み込み - Flask、Django など { #including-wsgi-flask-django-others }
|
||||
|
||||
[サブアプリケーション - マウント](sub-applications.md){.internal-link target=_blank}、[プロキシの背後](behind-a-proxy.md){.internal-link target=_blank} で見たように、WSGI アプリケーションをマウントできます。
|
||||
|
||||
そのために `WSGIMiddleware` を使用して、Flask や Django などの WSGI アプリをラップできます。
|
||||
|
||||
## `WSGIMiddleware` の使用 { #using-wsgimiddleware }
|
||||
|
||||
/// info | 情報
|
||||
|
||||
これには `a2wsgi` のインストールが必要です。例: `pip install a2wsgi`。
|
||||
|
||||
///
|
||||
|
||||
`a2wsgi` から `WSGIMiddleware` をインポートします。
|
||||
|
||||
次に、そのミドルウェアで WSGI(例: Flask)アプリをラップします。
|
||||
|
||||
そして、それをあるパスの下にマウントします。
|
||||
|
||||
{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *}
|
||||
|
||||
/// note | 備考
|
||||
|
||||
以前は `fastapi.middleware.wsgi` の `WSGIMiddleware` を使用することが推奨されていましたが、現在は非推奨です。
|
||||
|
||||
代わりに `a2wsgi` パッケージを使用することを推奨します。使い方は同じです。
|
||||
|
||||
`a2wsgi` パッケージがインストールされていることを確認し、`a2wsgi` から `WSGIMiddleware` を正しくインポートしてください。
|
||||
|
||||
///
|
||||
|
||||
## チェック { #check-it }
|
||||
|
||||
これで、パス `/v1/` 配下へのすべてのリクエストは Flask アプリケーションが処理します。
|
||||
|
||||
それ以外は **FastAPI** が処理します。
|
||||
|
||||
実行して <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a> にアクセスすると、Flask からのレスポンスが表示されます:
|
||||
|
||||
```txt
|
||||
Hello, World from Flask!
|
||||
```
|
||||
|
||||
さらに <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a> にアクセスすると、FastAPI からのレスポンスが表示されます:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"message": "Hello World"
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# クラウドプロバイダへの FastAPI デプロイ { #deploy-fastapi-on-cloud-providers }
|
||||
|
||||
FastAPI アプリケーションは、実質的にどのようなクラウドプロバイダでもデプロイできます。
|
||||
|
||||
多くの場合、主要なクラウドプロバイダは FastAPI をデプロイするためのガイドを提供しています。
|
||||
|
||||
## FastAPI Cloud { #fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** は、**FastAPI** の作者と同じチームによって作られています。
|
||||
|
||||
API の**構築**、**デプロイ**、**アクセス**までのプロセスを、最小限の手間で効率化します。
|
||||
|
||||
FastAPI でアプリを開発するときと同じ**開発者体験**を、クラウドへの**デプロイ**にももたらします。🎉
|
||||
|
||||
FastAPI Cloud は、*FastAPI and friends* オープンソースプロジェクトの主要なスポンサーかつ資金提供元です。✨
|
||||
|
||||
## クラウドプロバイダ - スポンサー { #cloud-providers-sponsors }
|
||||
|
||||
他にもいくつかのクラウドプロバイダが ✨ [**FastAPI をスポンサーしています**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨。🙇
|
||||
|
||||
それらのガイドを参考にし、サービスを試してみるのもよいでしょう:
|
||||
|
||||
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a>
|
||||
* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# FastAPI Cloud { #fastapi-cloud }
|
||||
|
||||
<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a> に **コマンド1つ** でデプロイできます。まだならウェイティングリストにご登録ください。🚀
|
||||
|
||||
## ログイン { #login }
|
||||
|
||||
すでに **FastAPI Cloud** アカウントをお持ちであることを確認してください(ウェイティングリストからご招待しています 😉)。
|
||||
|
||||
次にログインします:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi login
|
||||
|
||||
You are logged in to FastAPI Cloud 🚀
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## デプロイ { #deploy }
|
||||
|
||||
では、**コマンド1つ** でアプリをデプロイします:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi deploy
|
||||
|
||||
Deploying to FastAPI Cloud...
|
||||
|
||||
✅ Deployment successful!
|
||||
|
||||
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
以上です!その URL からアプリにアクセスできます。✨
|
||||
|
||||
## FastAPI Cloud について { #about-fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** は、**FastAPI** の作者とチームによって開発されています。
|
||||
|
||||
最小限の手間で API を**構築**・**デプロイ**・**利用**できるように工程を簡素化します。
|
||||
|
||||
FastAPI での開発と同じ**開発者体験**を、クラウドへの**デプロイ**にももたらします。🎉
|
||||
|
||||
さらに、アプリのデプロイ時に必要となる多くの事項も任せられます。例えば:
|
||||
|
||||
* HTTPS
|
||||
* レプリケーション(リクエストに基づくオートスケーリング付き)
|
||||
* など
|
||||
|
||||
FastAPI Cloud は、*FastAPI and friends* のオープンソースプロジェクトに対する主要スポンサーかつ資金提供元です。✨
|
||||
|
||||
## 他のクラウドプロバイダーへのデプロイ { #deploy-to-other-cloud-providers }
|
||||
|
||||
FastAPI はオープンソースで標準に基づいています。お好みの任意のクラウドプロバイダーに FastAPI アプリをデプロイできます。
|
||||
|
||||
各クラウドプロバイダーのガイドに従って FastAPI アプリをデプロイしてください。🤓
|
||||
|
||||
## 自分のサーバーへのデプロイ { #deploy-your-own-server }
|
||||
|
||||
この**デプロイ**ガイドの後半では詳細も解説します。何が起きているのか、何が必要なのか、そして自分のサーバーを含めて FastAPI アプリを自力でデプロイする方法が分かるようになります。🤓
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# FastAPI CLI { #fastapi-cli }
|
||||
|
||||
**FastAPI CLI** は、FastAPI アプリの提供、FastAPI プロジェクトの管理などに使用できるコマンドラインプログラムです。
|
||||
|
||||
FastAPI をインストールすると(例: `pip install "fastapi[standard]"`)、`fastapi-cli` というパッケージが含まれます。このパッケージがターミナルで使用する `fastapi` コマンドを提供します。
|
||||
|
||||
開発用に FastAPI アプリを起動するには、`fastapi dev` コマンドを使用できます:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
|
||||
|
||||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
|
||||
|
||||
Searching for package file structure from directories with
|
||||
<font color="#3465A4">__init__.py</font> files
|
||||
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
|
||||
following code:
|
||||
|
||||
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
|
||||
<b>fastapi run</b>
|
||||
|
||||
Logs:
|
||||
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
|
||||
<b>[</b><font color="#4E9A06">'/home/user/code/awesomeapp'</font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to
|
||||
quit<b>)</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
|
||||
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
|
||||
<span style="background-color="#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
`fastapi` というコマンドラインプログラムが **FastAPI CLI** です。
|
||||
|
||||
FastAPI CLI は、Python プログラムへのパス(例: `main.py`)を受け取り、`FastAPI` インスタンス(通常は `app`)を自動検出し、適切な import 方法を判断して提供します。
|
||||
|
||||
本番環境では代わりに `fastapi run` を使用します。🚀
|
||||
|
||||
内部的には、**FastAPI CLI** は <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>(高性能で本番運用向けの ASGI サーバー)を使用します。😎
|
||||
|
||||
## `fastapi dev` { #fastapi-dev }
|
||||
|
||||
`fastapi dev` を実行すると、開発モードが有効になります。
|
||||
|
||||
デフォルトでは、**auto-reload** が有効です。コードを変更するとサーバーが自動で再読み込みされます。これはリソースを多く消費し、無効時より安定性が低くなる可能性があります。開発時のみに使用してください。また、IP アドレス `127.0.0.1`(マシン自身のみと通信するための IP、`localhost`)で待ち受けます。
|
||||
|
||||
## `fastapi run` { #fastapi-run }
|
||||
|
||||
`fastapi run` を実行すると、デフォルトで本番モードで起動します。
|
||||
|
||||
デフォルトでは、**auto-reload** は無効です。また、IP アドレス `0.0.0.0`(利用可能なすべての IP アドレスを意味します)で待ち受けるため、そのマシンと通信できる任意のクライアントから公開アクセスが可能になります。これは、たとえばコンテナ内など、本番環境で一般的な実行方法です。
|
||||
|
||||
多くの場合(そして推奨されるのは)、上位に HTTPS を終端する「termination proxy」を置きます。これはアプリのデプロイ方法に依存し、プロバイダが代行する場合もあれば、自分で設定する必要がある場合もあります。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
詳しくは、[デプロイのドキュメント](deployment/index.md){.internal-link target=_blank}を参照してください。
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# 古い 403 認証エラーのステータスコードを使う { #use-old-403-authentication-error-status-codes }
|
||||
|
||||
FastAPI バージョン `0.122.0` より前は、統合されたセキュリティユーティリティが認証に失敗してクライアントへエラーを返す際、HTTP ステータスコード `403 Forbidden` を使用していました。
|
||||
|
||||
FastAPI バージョン `0.122.0` 以降では、より適切な HTTP ステータスコード `401 Unauthorized` を使用し、HTTP 仕様に従ってレスポンスに妥当な `WWW-Authenticate` ヘッダーを含めます。<a href="https://datatracker.ietf.org/doc/html/rfc7235#section-3.1" class="external-link" target="_blank">RFC 7235</a>、<a href="https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized" class="external-link" target="_blank">RFC 9110</a>。
|
||||
|
||||
しかし、何らかの理由でクライアントが従来の挙動に依存している場合は、セキュリティクラスでメソッド `make_not_authenticated_error` をオーバーライドすることで、その挙動に戻せます。
|
||||
|
||||
たとえば、既定の `401 Unauthorized` エラーの代わりに `403 Forbidden` エラーを返す `HTTPBearer` のサブクラスを作成できます:
|
||||
|
||||
{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
この関数は例外インスタンスを返す点に注意してください。ここでは例外を送出しません。送出は内部の他のコードで行われます。
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# Swagger UI の設定 { #configure-swagger-ui }
|
||||
|
||||
いくつかの追加の <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI パラメータ</a>を設定できます。
|
||||
|
||||
設定するには、`FastAPI()` のアプリオブジェクトを作成するとき、または `get_swagger_ui_html()` 関数に `swagger_ui_parameters` 引数を渡します。
|
||||
|
||||
`swagger_ui_parameters` は、Swagger UI に直接渡される設定を含む辞書を受け取ります。
|
||||
|
||||
FastAPI はそれらの設定を **JSON** に変換し、JavaScript と互換にします。Swagger UI が必要とするのはこの形式です。
|
||||
|
||||
## シンタックスハイライトを無効化 { #disable-syntax-highlighting }
|
||||
|
||||
例えば、Swagger UI のシンタックスハイライトを無効化できます。
|
||||
|
||||
設定を変更しなければ、シンタックスハイライトはデフォルトで有効です:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image02.png">
|
||||
|
||||
しかし、`syntaxHighlight` を `False` に設定すると無効化できます:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *}
|
||||
|
||||
...その場合、Swagger UI ではシンタックスハイライトが表示されなくなります:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image03.png">
|
||||
|
||||
## テーマの変更 { #change-the-theme }
|
||||
|
||||
同様に、キー `"syntaxHighlight.theme"`(途中にドットが含まれている点に注意)でシンタックスハイライトのテーマを設定できます:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *}
|
||||
|
||||
この設定により、シンタックスハイライトの配色テーマが変わります:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image04.png">
|
||||
|
||||
## 既定の Swagger UI パラメータの変更 { #change-default-swagger-ui-parameters }
|
||||
|
||||
FastAPI には、多くのユースケースに適した既定の設定パラメータが含まれています。
|
||||
|
||||
既定では次の設定が含まれます:
|
||||
|
||||
{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
|
||||
|
||||
引数 `swagger_ui_parameters` に別の値を指定することで、これらを上書きできます。
|
||||
|
||||
例えば、`deepLinking` を無効化するには、次の設定を `swagger_ui_parameters` に渡します:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *}
|
||||
|
||||
## その他の Swagger UI パラメータ { #other-swagger-ui-parameters }
|
||||
|
||||
利用可能な他のすべての設定については、公式の <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI パラメータのドキュメント</a>を参照してください。
|
||||
|
||||
## JavaScript 専用の設定 { #javascript-only-settings }
|
||||
|
||||
Swagger UI では、他にも **JavaScript 専用** のオブジェクト(例: JavaScript の関数)による設定が可能です。
|
||||
|
||||
FastAPI には、次の JavaScript 専用の `presets` 設定も含まれています:
|
||||
|
||||
```JavaScript
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
]
|
||||
```
|
||||
|
||||
これらは文字列ではなく **JavaScript** のオブジェクトであるため、Python のコードから直接渡すことはできません。
|
||||
|
||||
そのような JavaScript 専用の設定を使う必要がある場合は、上記のいずれかの方法を使用し、Swagger UI の path operation をオーバーライドして、必要な JavaScript を手動で記述してください。
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
# カスタムドキュメント UI の静的アセット(セルフホスティング) { #custom-docs-ui-static-assets-self-hosting }
|
||||
|
||||
API ドキュメントは **Swagger UI** と **ReDoc** を使用しており、それぞれにいくつかの JavaScript と CSS ファイルが必要です。
|
||||
|
||||
既定では、これらのファイルは <abbr title="Content Delivery Network - コンテンツ配信ネットワーク: 通常は複数のサーバーで構成され、JavaScript や CSS などの静的ファイルを提供するサービス。クライアントに近いサーバーからそれらのファイルを配信することで、パフォーマンスを改善するためによく使われます。">CDN</abbr> から配信されます。
|
||||
|
||||
しかし、カスタマイズすることも可能で、特定の CDN を指定したり、自分でファイルを配信したりできます。
|
||||
|
||||
## JavaScript と CSS のカスタム CDN { #custom-cdn-for-javascript-and-css }
|
||||
|
||||
別の <abbr title="Content Delivery Network - コンテンツ配信ネットワーク">CDN</abbr> を使いたいとします。例えば `https://unpkg.com/` を使いたい場合です。
|
||||
|
||||
例えば、一部の URL が制限されている国に住んでいる場合に役立ちます。
|
||||
|
||||
### 自動ドキュメントの無効化 { #disable-the-automatic-docs }
|
||||
|
||||
最初の手順は自動ドキュメントを無効化することです。デフォルトではそれらは既定の CDN を使用します。
|
||||
|
||||
無効化するには、`FastAPI` アプリ作成時にそれらの URL を `None` に設定します:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *}
|
||||
|
||||
### カスタムドキュメントの追加 { #include-the-custom-docs }
|
||||
|
||||
これで、カスタムドキュメント用の *path operations* を作成できます。
|
||||
|
||||
FastAPI の内部関数を再利用してドキュメント用の HTML ページを生成し、必要な引数を渡せます:
|
||||
|
||||
- `openapi_url`: ドキュメントの HTML ページが API の OpenAPI スキーマを取得する URL。ここでは属性 `app.openapi_url` を使用できます。
|
||||
- `title`: API のタイトル。
|
||||
- `oauth2_redirect_url`: 既定値を使うにはここで `app.swagger_ui_oauth2_redirect_url` を使用できます。
|
||||
- `swagger_js_url`: Swagger UI ドキュメント用の HTML が取得する JavaScript ファイルの URL。これはカスタム CDN の URL です。
|
||||
- `swagger_css_url`: Swagger UI ドキュメント用の HTML が取得する CSS ファイルの URL。これはカスタム CDN の URL です。
|
||||
|
||||
ReDoc についても同様です...
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`swagger_ui_redirect` 用の *path operation* は、OAuth2 を使用する場合の補助です。
|
||||
|
||||
API を OAuth2 プロバイダと統合すると、認証を実行して取得したクレデンシャルを持った状態で API ドキュメントに戻れます。そして実際の OAuth2 認証を用いてドキュメント上から API と対話できます。
|
||||
|
||||
Swagger UI がこの処理を裏側で行いますが、そのためにこの「redirect」の補助が必要です。
|
||||
|
||||
///
|
||||
|
||||
### テスト用の *path operation* を作成 { #create-a-path-operation-to-test-it }
|
||||
|
||||
すべてが動作するかをテストできるように、*path operation* を作成します:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *}
|
||||
|
||||
### テスト { #test-it }
|
||||
|
||||
これで、<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> にアクセスしてページを再読み込みすると、新しい CDN からそれらのアセットが読み込まれるはずです。
|
||||
|
||||
## ドキュメント用 JavaScript と CSS のセルフホスティング { #self-hosting-javascript-and-css-for-docs }
|
||||
|
||||
オフライン(インターネット非接続)でも、あるいはローカルネットワークで、アプリを動作させたい場合などには、JavaScript と CSS をセルフホストするのが有用です。
|
||||
|
||||
ここでは、同じ FastAPI アプリ内でそれらのファイルを配信し、ドキュメントでそれらを使用するように設定する方法を示します。
|
||||
|
||||
### プロジェクトのファイル構成 { #project-file-structure }
|
||||
|
||||
プロジェクトのファイル構成が次のようになっているとします:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
```
|
||||
|
||||
これらの静的ファイルを保存するためのディレクトリを作成します。
|
||||
|
||||
新しいファイル構成は次のようになります:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
└── static/
|
||||
```
|
||||
|
||||
### ファイルのダウンロード { #download-the-files }
|
||||
|
||||
ドキュメントに必要な静的ファイルをダウンロードし、`static/` ディレクトリに配置します。
|
||||
|
||||
各リンクを右クリックして「リンク先を別名で保存...」のようなオプションを選べます。
|
||||
|
||||
**Swagger UI** では次のファイルを使用します:
|
||||
|
||||
- <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a>
|
||||
- <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a>
|
||||
|
||||
そして **ReDoc** では次のファイルを使用します:
|
||||
|
||||
- <a href="https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a>
|
||||
|
||||
その後、ファイル構成は次のようになります:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
└── static
|
||||
├── redoc.standalone.js
|
||||
├── swagger-ui-bundle.js
|
||||
└── swagger-ui.css
|
||||
```
|
||||
|
||||
### 静的ファイルの配信 { #serve-the-static-files }
|
||||
|
||||
- `StaticFiles` をインポートします。
|
||||
- 特定のパスに `StaticFiles()` インスタンスを「マウント」します。
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *}
|
||||
|
||||
### 静的ファイルのテスト { #test-the-static-files }
|
||||
|
||||
アプリケーションを起動し、<a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a> にアクセスします。
|
||||
|
||||
**ReDoc** 用の非常に長い JavaScript ファイルが表示されるはずです。
|
||||
|
||||
先頭は次のようになっているかもしれません:
|
||||
|
||||
```JavaScript
|
||||
/*! ライセンス情報については redoc.standalone.js.LICENSE.txt を参照してください */
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
|
||||
...
|
||||
```
|
||||
|
||||
これで、アプリから静的ファイルを配信できていること、そしてドキュメント用の静的ファイルを正しい場所に配置できていることが確認できます。
|
||||
|
||||
次に、ドキュメントでそれらの静的ファイルを使用するようにアプリを設定します。
|
||||
|
||||
### 静的ファイル用に自動ドキュメントを無効化 { #disable-the-automatic-docs-for-static-files }
|
||||
|
||||
カスタム CDN を使う場合と同様、最初の手順は自動ドキュメントを無効化することです。既定では CDN を使用します。
|
||||
|
||||
無効化するには、`FastAPI` アプリ作成時にそれらの URL を `None` に設定します:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *}
|
||||
|
||||
### 静的ファイル用のカスタムドキュメントを追加 { #include-the-custom-docs-for-static-files }
|
||||
|
||||
カスタム CDN と同様の方法で、カスタムドキュメント用の *path operations* を作成できます。
|
||||
|
||||
再び、FastAPI の内部関数を再利用してドキュメント用の HTML ページを生成し、必要な引数を渡します:
|
||||
|
||||
- `openapi_url`: ドキュメントの HTML ページが API の OpenAPI スキーマを取得する URL。ここでは属性 `app.openapi_url` を使用できます。
|
||||
- `title`: API のタイトル。
|
||||
- `oauth2_redirect_url`: 既定値を使うにはここで `app.swagger_ui_oauth2_redirect_url` を使用できます。
|
||||
- `swagger_js_url`: Swagger UI ドキュメント用の HTML が取得する **JavaScript** ファイルの URL。**これはあなたのアプリ自身がいま配信しているものです**。
|
||||
- `swagger_css_url`: Swagger UI ドキュメント用の HTML が取得する **CSS** ファイルの URL。**これはあなたのアプリ自身がいま配信しているものです**。
|
||||
|
||||
ReDoc についても同様です...
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`swagger_ui_redirect` 用の *path operation* は、OAuth2 を使用する場合の補助です。
|
||||
|
||||
API を OAuth2 プロバイダと統合すると、認証を実行して取得したクレデンシャルを持った状態で API ドキュメントに戻れます。そして実際の OAuth2 認証を用いてドキュメント上から API と対話できます。
|
||||
|
||||
Swagger UI がこの処理を裏側で行いますが、そのためにこの「redirect」の補助が必要です。
|
||||
|
||||
///
|
||||
|
||||
### 静的ファイルをテストするための *path operation* を作成 { #create-a-path-operation-to-test-static-files }
|
||||
|
||||
すべてが動作するかをテストできるように、*path operation* を作成します:
|
||||
|
||||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *}
|
||||
|
||||
### 静的ファイル UI のテスト { #test-static-files-ui }
|
||||
|
||||
これで、WiFi を切断して <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> にアクセスし、ページを再読み込みできるはずです。
|
||||
|
||||
インターネットに接続していなくても、API のドキュメントを表示し、API と対話できます。
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# カスタム Request と APIRoute クラス { #custom-request-and-apiroute-class }
|
||||
|
||||
場合によっては、`Request` や `APIRoute` クラスで使われるロジックを上書きしたいことがあります。
|
||||
|
||||
特に、ミドルウェアでのロジックの代替として有効な場合があります。
|
||||
|
||||
たとえば、アプリケーションで処理される前にリクエストボディを読み取ったり操作したりしたい場合です。
|
||||
|
||||
/// danger | 警告
|
||||
|
||||
これは「上級」機能です。
|
||||
|
||||
FastAPI を始めたばかりの場合は、このセクションは読み飛ばしてもよいでしょう。
|
||||
|
||||
///
|
||||
|
||||
## ユースケース { #use-cases }
|
||||
|
||||
ユースケースの例:
|
||||
|
||||
* JSON ではないリクエストボディを JSON に変換する(例: <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>)。
|
||||
* gzip 圧縮されたリクエストボディの解凍。
|
||||
* すべてのリクエストボディの自動ロギング。
|
||||
|
||||
## カスタムリクエストボディのエンコーディングの処理 { #handling-custom-request-body-encodings }
|
||||
|
||||
gzip のリクエストを解凍するために、カスタムの `Request` サブクラスを使う方法を見ていきます。
|
||||
|
||||
そして、そのカスタムリクエストクラスを使うための `APIRoute` サブクラスを用意します。
|
||||
|
||||
### カスタム `GzipRequest` クラスの作成 { #create-a-custom-gziprequest-class }
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
これは仕組みを示すためのサンプルです。Gzip 対応が必要な場合は、用意されている [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank} を使用できます。
|
||||
|
||||
///
|
||||
|
||||
まず、`GzipRequest` クラスを作成します。これは適切なヘッダーがある場合に本体を解凍するよう、`Request.body()` メソッドを上書きします。
|
||||
|
||||
ヘッダーに `gzip` がなければ、解凍は試みません。
|
||||
|
||||
この方法により、同じルートクラスで gzip 圧縮済み/未圧縮のリクエストの両方を扱えます。
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *}
|
||||
|
||||
### カスタム `GzipRoute` クラスの作成 { #create-a-custom-gziproute-class }
|
||||
|
||||
次に、`GzipRequest` を利用する `fastapi.routing.APIRoute` のカスタムサブクラスを作成します。
|
||||
|
||||
ここでは `APIRoute.get_route_handler()` メソッドを上書きします。
|
||||
|
||||
このメソッドは関数を返します。そしてその関数がリクエストを受け取り、レスポンスを返します。
|
||||
|
||||
ここでは、元のリクエストから `GzipRequest` を作成するために利用します。
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *}
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
`Request` には `request.scope` 属性があり、これはリクエストに関するメタデータを含む Python の `dict` です。
|
||||
|
||||
`Request` には `request.receive` もあり、これはリクエストの本体を「受信」するための関数です。
|
||||
|
||||
`scope` の `dict` と `receive` 関数はいずれも ASGI 仕様の一部です。
|
||||
|
||||
そしてこの 2 つ(`scope` と `receive`)が、新しい `Request` インスタンスを作成するために必要なものです。
|
||||
|
||||
`Request` について詳しくは、<a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">Starlette の Requests に関するドキュメント</a> を参照してください。
|
||||
|
||||
///
|
||||
|
||||
`GzipRequest.get_route_handler` が返す関数が異なるのは、`Request` を `GzipRequest` に変換する点だけです。
|
||||
|
||||
これにより、`GzipRequest` は必要に応じてデータを解凍してから *path operations* に渡します。
|
||||
|
||||
それ以降の処理ロジックはすべて同じです。
|
||||
|
||||
ただし、`GzipRequest.body` を変更しているため、必要に応じて **FastAPI** によって読み込まれる際にリクエストボディが自動的に解凍されます。
|
||||
|
||||
## 例外ハンドラでのリクエストボディへのアクセス { #accessing-the-request-body-in-an-exception-handler }
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
同じ問題を解決するには、`RequestValidationError` 用のカスタムハンドラで `body` を使う方がずっと簡単でしょう([エラー処理](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank})。
|
||||
|
||||
ただし、この例も有効で、内部コンポーネントとどのようにやり取りするかを示しています。
|
||||
|
||||
///
|
||||
|
||||
同じアプローチを使って、例外ハンドラ内でリクエストボディにアクセスすることもできます。
|
||||
|
||||
やることは、`try`/`except` ブロックの中でリクエストを処理するだけです:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *}
|
||||
|
||||
例外が発生しても、`Request` インスタンスはスコープ内に残るため、エラー処理時にリクエストボディを読み取り、活用できます:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *}
|
||||
|
||||
## ルーターでのカスタム `APIRoute` クラス { #custom-apiroute-class-in-a-router }
|
||||
|
||||
`APIRouter` の `route_class` パラメータを設定することもできます:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *}
|
||||
|
||||
この例では、`router` 配下の *path operations* はカスタムの `TimedRoute` クラスを使用し、レスポンスの生成にかかった時間を示す追加の `X-Response-Time` ヘッダーがレスポンスに含まれます:
|
||||
|
||||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
# OpenAPI の拡張 { #extending-openapi }
|
||||
|
||||
生成された OpenAPI スキーマを変更する必要がある場合があります。
|
||||
|
||||
このセクションではその方法を説明します。
|
||||
|
||||
## 通常のプロセス { #the-normal-process }
|
||||
|
||||
通常(デフォルト)のプロセスは次のとおりです。
|
||||
|
||||
`FastAPI` アプリケーション(インスタンス)には、OpenAPI スキーマを返すことが期待される `.openapi()` メソッドがあります。
|
||||
|
||||
アプリケーションオブジェクトの作成時に、`/openapi.json`(または `openapi_url` に設定したパス)への path operation が登録されます。
|
||||
|
||||
これは単に、アプリケーションの `.openapi()` メソッドの結果を含む JSON レスポンスを返します。
|
||||
|
||||
デフォルトでは、`.openapi()` メソッドはプロパティ `.openapi_schema` に内容があるかを確認し、あればそれを返します。
|
||||
|
||||
なければ、`fastapi.openapi.utils.get_openapi` にあるユーティリティ関数を使って生成します。
|
||||
|
||||
この関数 `get_openapi()` は次の引数を受け取ります:
|
||||
|
||||
- `title`: ドキュメントに表示される OpenAPI のタイトル。
|
||||
- `version`: API のバージョン。例: `2.5.0`。
|
||||
- `openapi_version`: 使用する OpenAPI 仕様のバージョン。デフォルトは最新の `3.1.0`。
|
||||
- `summary`: API の短い概要。
|
||||
- `description`: API の説明。Markdown を含めることができ、ドキュメントに表示されます。
|
||||
- `routes`: ルートのリスト。登録済みの各 path operation です。`app.routes` から取得されます。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
パラメータ `summary` は OpenAPI 3.1.0 以降で利用可能で、FastAPI 0.99.0 以降が対応しています。
|
||||
|
||||
///
|
||||
|
||||
## デフォルトの上書き { #overriding-the-defaults }
|
||||
|
||||
上記の情報を使って、同じユーティリティ関数で OpenAPI スキーマを生成し、必要な部分を上書きできます。
|
||||
|
||||
たとえば、<a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">カスタムロゴを含めるための ReDoc の OpenAPI 拡張</a>を追加してみましょう。
|
||||
|
||||
### 通常の **FastAPI** { #normal-fastapi }
|
||||
|
||||
まず、通常どおりに **FastAPI** アプリケーションを実装します:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *}
|
||||
|
||||
### OpenAPI スキーマの生成 { #generate-the-openapi-schema }
|
||||
|
||||
次に、`custom_openapi()` 関数内で同じユーティリティ関数を使って OpenAPI スキーマを生成します:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *}
|
||||
|
||||
### OpenAPI スキーマの変更 { #modify-the-openapi-schema }
|
||||
|
||||
OpenAPI スキーマの `info`「オブジェクト」にカスタムの `x-logo` を追加して、ReDoc 拡張を加えます:
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *}
|
||||
|
||||
### OpenAPI スキーマのキャッシュ { #cache-the-openapi-schema }
|
||||
|
||||
生成したスキーマを保持する「キャッシュ」として `.openapi_schema` プロパティを利用できます。
|
||||
|
||||
こうすることで、ユーザーが API ドキュメントを開くたびにスキーマを生成する必要がなくなります。
|
||||
|
||||
最初の1回だけ生成され、その後は同じキャッシュ済みスキーマが以降のリクエストで使われます。
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *}
|
||||
|
||||
### メソッドの上書き { #override-the-method }
|
||||
|
||||
これで、`.openapi()` メソッドを新しい関数に置き換えられます。
|
||||
|
||||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *}
|
||||
|
||||
### 確認 { #check-it }
|
||||
|
||||
<a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a> にアクセスすると、カスタムロゴ(この例では **FastAPI** のロゴ)が使われていることが確認できます:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image01.png">
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# 一般 - ハウツー - レシピ { #general-how-to-recipes }
|
||||
|
||||
ここでは、一般的またはよくある質問に対して、ドキュメント内の他の箇所への参照をいくつか示します。
|
||||
|
||||
## データのフィルタリング - セキュリティ { #filter-data-security }
|
||||
|
||||
返すべき以上のデータを返さないようにするには、[チュートリアル - レスポンスモデル - 戻り値の型](../tutorial/response-model.md){.internal-link target=_blank} を参照してください。
|
||||
|
||||
## ドキュメントのタグ - OpenAPI { #documentation-tags-openapi }
|
||||
|
||||
*path operations* にタグを追加し、ドキュメント UI でグループ化するには、[チュートリアル - path operation の設定 - タグ](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank} を参照してください。
|
||||
|
||||
## ドキュメントの概要と説明 - OpenAPI { #documentation-summary-and-description-openapi }
|
||||
|
||||
*path operations* に概要と説明を追加し、ドキュメント UI に表示するには、[チュートリアル - path operation の設定 - 概要と説明](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank} を参照してください。
|
||||
|
||||
## ドキュメントのレスポンス説明 - OpenAPI { #documentation-response-description-openapi }
|
||||
|
||||
ドキュメント UI に表示されるレスポンスの説明を定義するには、[チュートリアル - path operation の設定 - レスポンスの説明](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank} を参照してください。
|
||||
|
||||
## *Path Operation* の非推奨化 - OpenAPI { #documentation-deprecate-a-path-operation-openapi }
|
||||
|
||||
*path operation* を非推奨にし、ドキュメント UI に表示するには、[チュートリアル - path operation の設定 - 非推奨](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank} を参照してください。
|
||||
|
||||
## 任意のデータを JSON 互換に変換 { #convert-any-data-to-json-compatible }
|
||||
|
||||
任意のデータを JSON 互換に変換するには、[チュートリアル - JSON 互換エンコーダ](../tutorial/encoder.md){.internal-link target=_blank} を参照してください。
|
||||
|
||||
## OpenAPI メタデータ - ドキュメント { #openapi-metadata-docs }
|
||||
|
||||
ライセンス、バージョン、連絡先などを含むメタデータを OpenAPI スキーマに追加するには、[チュートリアル - メタデータとドキュメントの URL](../tutorial/metadata.md){.internal-link target=_blank} を参照してください。
|
||||
|
||||
## OpenAPI のカスタム URL { #openapi-custom-url }
|
||||
|
||||
OpenAPI の URL をカスタマイズ(または削除)するには、[チュートリアル - メタデータとドキュメントの URL](../tutorial/metadata.md#openapi-url){.internal-link target=_blank} を参照してください。
|
||||
|
||||
## OpenAPI ドキュメントの URL { #openapi-docs-urls }
|
||||
|
||||
自動生成されるドキュメント UI が使用する URL を変更するには、[チュートリアル - メタデータとドキュメントの URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank} を参照してください。
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# GraphQL { #graphql }
|
||||
|
||||
**FastAPI** は **ASGI** 標準に基づいているため、ASGI に対応した任意の **GraphQL** ライブラリを簡単に統合できます。
|
||||
|
||||
同じアプリケーション内で通常の FastAPI の *path operation* と GraphQL を組み合わせて使えます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
**GraphQL** は非常に特定のユースケースを解決します。
|
||||
|
||||
一般的な **Web API** と比べると、**長所** と **短所** があります。
|
||||
|
||||
ご自身のユースケースで得られる **利点** が **欠点** を補うかどうかを評価してください。 🤓
|
||||
|
||||
///
|
||||
|
||||
## GraphQL ライブラリ { #graphql-libraries }
|
||||
|
||||
**ASGI** をサポートする **GraphQL** ライブラリの一部を以下に示します。**FastAPI** と組み合わせて使用できます:
|
||||
|
||||
* <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> 🍓
|
||||
* <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">FastAPI 向けドキュメント</a>あり
|
||||
* <a href="https://ariadnegraphql.org/" class="external-link" target="_blank">Ariadne</a>
|
||||
* <a href="https://ariadnegraphql.org/docs/fastapi-integration" class="external-link" target="_blank">FastAPI 向けドキュメント</a>あり
|
||||
* <a href="https://tartiflette.io/" class="external-link" target="_blank">Tartiflette</a>
|
||||
* ASGI 連携用の <a href="https://tartiflette.github.io/tartiflette-asgi/" class="external-link" target="_blank">Tartiflette ASGI</a> あり
|
||||
* <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a>
|
||||
* <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a> あり
|
||||
|
||||
## Strawberry で GraphQL { #graphql-with-strawberry }
|
||||
|
||||
**GraphQL** が必要、または利用したい場合は、<a href="https://strawberry.rocks/" class="external-link" target="_blank">**Strawberry**</a> を**推奨**します。**FastAPI** の設計に最も近く、すべてが**型アノテーション**に基づいています。
|
||||
|
||||
ユースケースによっては他のライブラリを選ぶ方がよい場合もありますが、私に尋ねられれば、おそらく **Strawberry** を試すことを勧めるでしょう。
|
||||
|
||||
FastAPI と Strawberry を統合する方法の簡単なプレビューです:
|
||||
|
||||
{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *}
|
||||
|
||||
詳細は <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry のドキュメント</a>をご覧ください。
|
||||
|
||||
また、<a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">Strawberry と FastAPI</a> の連携に関するドキュメントもあります。
|
||||
|
||||
## Starlette の旧 `GraphQLApp` { #older-graphqlapp-from-starlette }
|
||||
|
||||
以前の Starlette には、<a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a> と統合するための `GraphQLApp` クラスが含まれていました。
|
||||
|
||||
これは Starlette からは非推奨になりましたが、もしそれを使用しているコードがある場合は、同じユースケースをカバーし、**ほぼ同一のインターフェース**を持つ <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a> へ容易に**移行**できます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
GraphQL が必要であれば、依然として <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> の利用を推奨します。独自のクラスや型ではなく、型アノテーションに基づいているためです。
|
||||
|
||||
///
|
||||
|
||||
## さらに学ぶ { #learn-more }
|
||||
|
||||
**GraphQL** については、<a href="https://graphql.org/" class="external-link" target="_blank">公式 GraphQL ドキュメント</a>でさらに学べます。
|
||||
|
||||
上記の各ライブラリについては、リンク先のドキュメントをご参照ください。
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# ハウツー - レシピ { #how-to-recipes }
|
||||
|
||||
ここでは、**複数のトピック**に関するさまざまなレシピや「ハウツー」ガイドを紹介します。
|
||||
|
||||
これらのアイデアの多くはおおむね**独立**しており、ほとんどの場合、**あなたのプロジェクト**に直接当てはまるものだけを読めば十分です。
|
||||
|
||||
プロジェクトにとって興味深く有用だと思うものがあれば、ぜひ確認してください。そうでなければ、読み飛ばしても問題ありません。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
**FastAPI を学ぶ**ことを体系的に進めたい場合(推奨)、代わりに [チュートリアル - ユーザーガイド](../tutorial/index.md){.internal-link target=_blank} を章ごとに読んでください。
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
# Pydantic v1 から Pydantic v2 への移行 { #migrate-from-pydantic-v1-to-pydantic-v2 }
|
||||
|
||||
古い FastAPI アプリがある場合、Pydantic v1 を使っているかもしれません。
|
||||
|
||||
FastAPI 0.100.0 は Pydantic v1 / v2 のどちらにも対応しており、インストールされている方を使用しました。
|
||||
|
||||
FastAPI 0.119.0 では、Pydantic v2 内からの Pydantic v1 の部分的サポート(`pydantic.v1`)が導入され、v2 への移行が容易になりました。
|
||||
|
||||
FastAPI 0.126.0 で Pydantic v1 のサポートは終了しましたが、しばらくの間は `pydantic.v1` は利用可能でした。
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
Pydantic チームは Python の最新バージョン、つまり **Python 3.14** から、Pydantic v1 のサポートを終了しました。
|
||||
|
||||
これには `pydantic.v1` も含まれ、Python 3.14 以上ではサポートされません。
|
||||
|
||||
Python の最新機能を使いたい場合は、Pydantic v2 を使用していることを確認する必要があります。
|
||||
|
||||
///
|
||||
|
||||
古い FastAPI アプリで Pydantic v1 を使っている場合、ここでは Pydantic v2 への移行方法と、段階的移行を助ける **FastAPI 0.119.0 の機能** を紹介します。
|
||||
|
||||
## 公式ガイド { #official-guide }
|
||||
|
||||
Pydantic には v1 から v2 への公式の <a href="https://docs.pydantic.dev/latest/migration/" class="external-link" target="_blank">移行ガイド</a> があります。
|
||||
|
||||
変更点、検証がより正確で厳密になった点、注意事項などが含まれます。
|
||||
|
||||
何が変わったかをよりよく理解するために参照してください。
|
||||
|
||||
## テスト { #tests }
|
||||
|
||||
アプリに対する[テスト](../tutorial/testing.md){.internal-link target=_blank}を用意し、継続的インテグレーション(CI)で実行するようにしてください。
|
||||
|
||||
これにより、アップグレード後も期待どおり動作していることを確認できます。
|
||||
|
||||
## `bump-pydantic` { #bump-pydantic }
|
||||
|
||||
多くの場合、カスタマイズのない通常の Pydantic モデルを使っていれば、v1 から v2 への移行作業の大半を自動化できます。
|
||||
|
||||
同じ Pydantic チームが提供する <a href="https://github.com/pydantic/bump-pydantic" class="external-link" target="_blank">`bump-pydantic`</a> を使用できます。
|
||||
|
||||
このツールは必要なコード変更のほとんどを自動で行います。
|
||||
|
||||
その後テストを実行し、問題なければ完了です。😎
|
||||
|
||||
## v2 における Pydantic v1 { #pydantic-v1-in-v2 }
|
||||
|
||||
Pydantic v2 には、Pydantic v1 がサブモジュール `pydantic.v1` として同梱されています。ただし、これは Python 3.13 を超えるバージョンではサポートされません。
|
||||
|
||||
つまり、Pydantic v2 の最新バージョンをインストールし、このサブモジュールから旧 Pydantic v1 のコンポーネントをインポートして、あたかも v1 をインストールしているかのように使用できます。
|
||||
|
||||
{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
|
||||
|
||||
### v2 内の Pydantic v1 に対する FastAPI のサポート { #fastapi-support-for-pydantic-v1-in-v2 }
|
||||
|
||||
FastAPI 0.119.0 以降では、移行を容易にするため、Pydantic v2 内の Pydantic v1 に対する部分的サポートもあります。
|
||||
|
||||
そのため、Pydantic を v2 の最新に上げ、インポートを `pydantic.v1` サブモジュールに切り替えるだけで、多くの場合そのまま動作します。
|
||||
|
||||
{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
前述のとおり、Python の最近のバージョン(Python 3.14 以降)では Pydantic v1 がサポートされないため、`pydantic.v1` の使用も Python 3.14 以上ではサポートされません。
|
||||
|
||||
///
|
||||
|
||||
### 同一アプリでの Pydantic v1 と v2 { #pydantic-v1-and-v2-on-the-same-app }
|
||||
|
||||
Pydantic v2 のモデルのフィールドに Pydantic v1 のモデルを(またはその逆を)埋め込むことは、Pydantic では「サポートされていません」。
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "❌ Not Supported"
|
||||
direction TB
|
||||
subgraph V2["Pydantic v2 Model"]
|
||||
V1Field["Pydantic v1 Model"]
|
||||
end
|
||||
subgraph V1["Pydantic v1 Model"]
|
||||
V2Field["Pydantic v2 Model"]
|
||||
end
|
||||
end
|
||||
|
||||
style V2 fill:#f9fff3
|
||||
style V1 fill:#fff6f0
|
||||
style V1Field fill:#fff6f0
|
||||
style V2Field fill:#f9fff3
|
||||
```
|
||||
|
||||
...but, you can have separated models using Pydantic v1 and v2 in the same app.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "✅ Supported"
|
||||
direction TB
|
||||
subgraph V2["Pydantic v2 Model"]
|
||||
V2Field["Pydantic v2 Model"]
|
||||
end
|
||||
subgraph V1["Pydantic v1 Model"]
|
||||
V1Field["Pydantic v1 Model"]
|
||||
end
|
||||
end
|
||||
|
||||
style V2 fill:#f9fff3
|
||||
style V1 fill:#fff6f0
|
||||
style V1Field fill:#fff6f0
|
||||
style V2Field fill:#f9fff3
|
||||
```
|
||||
|
||||
場合によっては、同じ FastAPI の path operation 内で、Pydantic v1 と v2 の両方のモデルを扱うことも可能です:
|
||||
|
||||
{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
|
||||
|
||||
上の例では、入力モデルは Pydantic v1、出力モデル(`response_model=ItemV2` で定義)は Pydantic v2 です。
|
||||
|
||||
### Pydantic v1 のパラメータ { #pydantic-v1-parameters }
|
||||
|
||||
Pydantic v1 のモデルで `Body`、`Query`、`Form` などの FastAPI 固有のパラメータユーティリティを使う必要がある場合、v2 への移行が完了するまでの間は `fastapi.temp_pydantic_v1_params` からインポートできます:
|
||||
|
||||
{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
|
||||
|
||||
### 段階的に移行 { #migrate-in-steps }
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
まずは `bump-pydantic` を試してください。テストが通り、問題なければコマンド一発で完了です。✨
|
||||
|
||||
///
|
||||
|
||||
`bump-pydantic` が適用できない場合は、同一アプリで v1 と v2 のモデルを併用できるサポートを利用して、徐々に v2 へ移行できます。
|
||||
|
||||
まず Pydantic を v2 の最新にアップグレードし、すべてのモデルのインポートを `pydantic.v1` に切り替えます。
|
||||
|
||||
その後、モデルをグループごとに少しずつ Pydantic v1 から v2 へ移行していきます。🚶
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
# 入力と出力でOpenAPIのスキーマを分けるかどうか { #separate-openapi-schemas-for-input-and-output-or-not }
|
||||
|
||||
**Pydantic v2** のリリース以降、生成される OpenAPI は以前より少し正確で、より正しいものになりました。😎
|
||||
|
||||
実際には、場合によっては同じ Pydantic モデルに対して、入力用と出力用で OpenAPI に **2 つの JSON Schema** が含まれることがあります。これは **デフォルト値** の有無に依存します。
|
||||
|
||||
その動作と、必要に応じての変更方法を見ていきます。
|
||||
|
||||
## 入出力のPydanticモデル { #pydantic-models-for-input-and-output }
|
||||
|
||||
次のようにデフォルト値を持つ Pydantic モデルがあるとします。
|
||||
|
||||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *}
|
||||
|
||||
### 入力用モデル { #model-for-input }
|
||||
|
||||
このモデルを次のように入力として使うと:
|
||||
|
||||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *}
|
||||
|
||||
...`description` フィールドは **必須ではありません**。デフォルト値が `None` だからです。
|
||||
|
||||
### ドキュメントでの入力モデル { #input-model-in-docs }
|
||||
|
||||
ドキュメントで確認すると、`description` フィールドには **赤いアスタリスク** が付いておらず、必須としてはマークされていません:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image01.png">
|
||||
</div>
|
||||
|
||||
### 出力用モデル { #model-for-output }
|
||||
|
||||
しかし同じモデルを次のように出力として使う場合:
|
||||
|
||||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *}
|
||||
|
||||
...`description` にデフォルト値があるため、そのフィールドに何も返さなくても、その **デフォルト値** が入ります。
|
||||
|
||||
### 出力のレスポンスデータ { #model-for-output-response-data }
|
||||
|
||||
ドキュメントから試してレスポンスを確認すると、コードでは一方の `description` フィールドに何も追加していないにもかかわらず、JSON レスポンスにはデフォルト値(`null`)が含まれています:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image02.png">
|
||||
</div>
|
||||
|
||||
つまりそのフィールドには **常に値があります**。値が `None`(JSON では `null`)になることがあるだけです。
|
||||
|
||||
したがって、この API を使うクライアントは値の有無を確認する必要がなく、フィールドが **常に存在する** と仮定できます。場合によってはデフォルト値の `None` になるだけです。
|
||||
|
||||
これを OpenAPI で表現するには、そのフィールドを **必須** としてマークします。常に存在するためです。
|
||||
|
||||
このため、モデルの JSON Schema は、**入力か出力か** によって異なる場合があります:
|
||||
|
||||
- **入力** では `description` は **必須ではない**
|
||||
- **出力** では **必須**(値は `None`、JSON では `null` の可能性あり)
|
||||
|
||||
### ドキュメントでの出力モデル { #model-for-output-in-docs }
|
||||
|
||||
ドキュメントで出力モデルを見ると、`name` と `description` の **両方** が **赤いアスタリスク** で **必須** としてマークされています:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image03.png">
|
||||
</div>
|
||||
|
||||
### ドキュメントでの入力・出力モデル { #model-for-input-and-output-in-docs }
|
||||
|
||||
さらに、OpenAPI に含まれる利用可能なスキーマ(JSON Schema)を確認すると、`Item-Input` と `Item-Output` の 2 つがあることが分かります。
|
||||
|
||||
`Item-Input` では、`description` は **必須ではありません**(赤いアスタリスクなし)。
|
||||
|
||||
一方、`Item-Output` では、`description` は **必須**(赤いアスタリスクあり)です。
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image04.png">
|
||||
</div>
|
||||
|
||||
この **Pydantic v2** の機能により、API ドキュメントはより **正確** になり、自動生成されたクライアントや SDK もより正確になります。これにより、より良い **開発者エクスペリエンス** と一貫性が得られます。🎉
|
||||
|
||||
## スキーマを分けない { #do-not-separate-schemas }
|
||||
|
||||
一方で、**入力と出力で同じスキーマ** にしたい場合もあります。
|
||||
|
||||
主なユースケースは、すでに自動生成されたクライアントコードや SDK があり、まだそれらをすべて更新したくない場合です。いずれは更新したいとしても、今ではないかもしれません。
|
||||
|
||||
その場合は、**FastAPI** のパラメータ `separate_input_output_schemas=False` でこの機能を無効化できます。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`separate_input_output_schemas` のサポートは FastAPI `0.102.0` で追加されました。🤓
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *}
|
||||
|
||||
### ドキュメントで入力・出力に同一スキーマを使用 { #same-schema-for-input-and-output-models-in-docs }
|
||||
|
||||
これでモデルの入力と出力は単一のスキーマ、`Item` のみになり、`description` は **必須ではありません**:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/separate-openapi-schemas/image05.png">
|
||||
</div>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# データベースのテスト { #testing-a-database }
|
||||
|
||||
データベース、SQL、SQLModel については、<a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel ドキュメント</a>で学べます。🤓
|
||||
|
||||
FastAPI と一緒に SQLModel を使うためのミニ <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">チュートリアル</a>があります。✨
|
||||
|
||||
そのチュートリアルには、<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">SQL データベースのテスト</a>に関するセクションも含まれています。😎
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# リソース { #resources }
|
||||
|
||||
追加リソース、外部リンクなど。✈️
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/// details | 🌐 AI と人間による翻訳
|
||||
|
||||
この翻訳は、人間のガイドに基づいて AI によって作成されました。🤝
|
||||
|
||||
原文の意図を取り違えていたり、不自然な表現になっている可能性があります。🤖
|
||||
|
||||
[AI LLM をより適切に誘導するのを手伝う](https://fastapi.tiangolo.com/ja/contributing/#translations) ことで、この翻訳を改善できます。
|
||||
|
||||
[英語版](ENGLISH_VERSION_URL)
|
||||
|
||||
///
|
||||
|
|
@ -0,0 +1,504 @@
|
|||
# 大規模アプリケーション - 複数ファイル { #bigger-applications-multiple-files }
|
||||
|
||||
アプリケーションや Web API を作る場合、すべてを1つのファイルに収められることはほとんどありません。
|
||||
|
||||
**FastAPI** は、柔軟性を保ったままアプリケーションを構造化できる便利なツールを提供します。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
Flask 出身であれば、Flask の Blueprint に相当します。
|
||||
|
||||
///
|
||||
|
||||
## 例のファイル構成 { #an-example-file-structure }
|
||||
|
||||
次のようなファイル構成があるとします:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ ├── dependencies.py
|
||||
│ └── routers
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── items.py
|
||||
│ │ └── users.py
|
||||
│ └── internal
|
||||
│ ├── __init__.py
|
||||
│ └── admin.py
|
||||
```
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
複数の `__init__.py` ファイルがあります: 各ディレクトリやサブディレクトリに1つずつです。
|
||||
|
||||
これにより、あるファイルから別のファイルへコードをインポートできます。
|
||||
|
||||
例えば、`app/main.py` では次のように書けます:
|
||||
|
||||
```
|
||||
from app.routers import items
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
* `app` ディレクトリはすべてを含みます。そして空のファイル `app/__init__.py` があり、「Python パッケージ」(「Python モジュール」の集合): `app` です。
|
||||
* `app/main.py` ファイルがあります。Python パッケージ(`__init__.py` のあるディレクトリ)の中にあるため、そのパッケージの「モジュール」: `app.main` です。
|
||||
* `app/dependencies.py` ファイルもあり、`app/main.py` と同様に「モジュール」: `app.dependencies` です。
|
||||
* `app/routers/` サブディレクトリに別の `__init__.py` があるので、「Python サブパッケージ」: `app.routers` です。
|
||||
* `app/routers/items.py` はパッケージ `app/routers/` 内のファイルなので、サブモジュール: `app.routers.items` です。
|
||||
* `app/routers/users.py` も同様で、別のサブモジュール: `app.routers.users` です。
|
||||
* `app/internal/` サブディレクトリにも `__init__.py` があるので、別の「Python サブパッケージ」: `app.internal` です。
|
||||
* `app/internal/admin.py` は別のサブモジュール: `app.internal.admin` です。
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
|
||||
|
||||
同じファイル構成にコメントを付けると次のとおりです:
|
||||
|
||||
```bash
|
||||
.
|
||||
├── app # "app" is a Python package
|
||||
│ ├── __init__.py # this file makes "app" a "Python package"
|
||||
│ ├── main.py # "main" module, e.g. import app.main
|
||||
│ ├── dependencies.py # "dependencies" module, e.g. import app.dependencies
|
||||
│ └── routers # "routers" is a "Python subpackage"
|
||||
│ │ ├── __init__.py # makes "routers" a "Python subpackage"
|
||||
│ │ ├── items.py # "items" submodule, e.g. import app.routers.items
|
||||
│ │ └── users.py # "users" submodule, e.g. import app.routers.users
|
||||
│ └── internal # "internal" is a "Python subpackage"
|
||||
│ ├── __init__.py # makes "internal" a "Python subpackage"
|
||||
│ └── admin.py # "admin" submodule, e.g. import app.internal.admin
|
||||
```
|
||||
|
||||
## `APIRouter` { #apirouter }
|
||||
|
||||
ユーザーだけを扱うファイルが `/app/routers/users.py` のサブモジュールだとします。
|
||||
|
||||
ユーザーに関連する *path operations* をほかのコードから分離して整理したいはずです。
|
||||
|
||||
ただし、同じ **FastAPI** アプリケーション / Web API(同じ「Python パッケージ」の一部)である点は変わりません。
|
||||
|
||||
そのモジュールで `APIRouter` を使って *path operations* を作成できます。
|
||||
|
||||
### `APIRouter` のインポート { #import-apirouter }
|
||||
|
||||
クラス `FastAPI` と同様にインポートし、「インスタンス」を作成します:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *}
|
||||
|
||||
### `APIRouter` での *path operations* { #path-operations-with-apirouter }
|
||||
|
||||
これを使って *path operations* を宣言します。
|
||||
|
||||
使い方は `FastAPI` クラスと同じです:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
|
||||
|
||||
`APIRouter` は「ミニ `FastAPI`」のようなクラスと考えられます。
|
||||
|
||||
同じオプションがすべてサポートされています。
|
||||
|
||||
同じ `parameters`、`responses`、`dependencies`、`tags` などが使えます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
この例では変数名は `router` ですが、任意の名前を付けられます。
|
||||
|
||||
///
|
||||
|
||||
この `APIRouter` をメインの `FastAPI` アプリに取り込みますが、その前に依存関係と別の `APIRouter` を確認します。
|
||||
|
||||
## 依存関係 { #dependencies }
|
||||
|
||||
アプリケーションの複数箇所で使う依存関係が必要になります。
|
||||
|
||||
そのため、専用の `dependencies` モジュール(`app/dependencies.py`)に置きます。
|
||||
|
||||
ここではカスタムヘッダー `X-Token` を読む簡単な依存関係を使います:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
この例を簡単にするために架空のヘッダーを使っています。
|
||||
|
||||
しかし実際には、組み込みの [Security utilities](security/index.md){.internal-link target=_blank} を使う方が良い結果になります。
|
||||
|
||||
///
|
||||
|
||||
## 別モジュールでの `APIRouter` { #another-module-with-apirouter }
|
||||
|
||||
アプリケーションの「items」を扱うエンドポイントが `app/routers/items.py` のモジュールにあるとします。
|
||||
|
||||
次の *path operations* があります:
|
||||
|
||||
* `/items/`
|
||||
* `/items/{item_id}`
|
||||
|
||||
構造は `app/routers/users.py` と同じです。
|
||||
|
||||
しかし、もう少し賢くしてコードを少し簡潔にしたいところです。
|
||||
|
||||
このモジュールのすべての *path operations* には同じものがあると分かっています:
|
||||
|
||||
* パスの `prefix`: `/items`
|
||||
* `tags`(1つのタグ: `items`)
|
||||
* 追加の `responses`
|
||||
* `dependencies`: 先ほど作成した `X-Token` の依存関係が必要
|
||||
|
||||
そこで、各 *path operation* に個別に追加する代わりに、これらを `APIRouter` に追加できます。
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
|
||||
|
||||
各 *path operation* のパスは次のように `/` で始める必要があるため:
|
||||
|
||||
```Python hl_lines="1"
|
||||
@router.get("/{item_id}")
|
||||
async def read_item(item_id: str):
|
||||
...
|
||||
```
|
||||
|
||||
...prefix の末尾に `/` を含めてはいけません。
|
||||
|
||||
この場合の prefix は `/items` です。
|
||||
|
||||
また、`tags` のリストや追加の `responses` を、このルーターに含まれるすべての *path operations* に適用するよう追加できます。
|
||||
|
||||
さらに `dependencies` のリストを追加できます。これはこのルーター内のすべての *path operations* に追加され、それらへの各リクエストごとに実行・解決されます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
[*path operation デコレータ*の依存関係](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} と同様に、*path operation 関数*には値は渡されない点に注意してください。
|
||||
|
||||
///
|
||||
|
||||
最終的に、item のパスは次のとおりになります:
|
||||
|
||||
* `/items/`
|
||||
* `/items/{item_id}`
|
||||
|
||||
...意図したとおりです。
|
||||
|
||||
* これらには、文字列 `"items"` を1つ含むタグのリストが付きます。
|
||||
* これらの「タグ」は、(OpenAPI を使う)自動インタラクティブドキュメントで特に有用です。
|
||||
* すべてに事前定義した `responses` が含まれます。
|
||||
* これらすべての *path operations* では、実行前に `dependencies` のリストが評価・実行されます。
|
||||
* 特定の *path operation* に依存関係を宣言した場合は、**それらも実行されます**。
|
||||
* ルーターの依存関係が先に実行され、その後に[デコレータ内の `dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}、次に通常のパラメータ依存関係が続きます。
|
||||
* [`scopes` を伴う `Security` 依存関係](../advanced/security/oauth2-scopes.md){.internal-link target=_blank} を追加することもできます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
`APIRouter` に `dependencies` を置くことで、*path operations* のグループ全体に認証を要求する、といった用途に使えます。個々の *path operation* に依存関係を追加していなくても構いません。
|
||||
|
||||
///
|
||||
|
||||
/// check | 確認
|
||||
|
||||
`prefix`、`tags`、`responses`、`dependencies` の各パラメータは(ほかの多くのケースと同様に)コード重複を避けるための **FastAPI** の機能です。
|
||||
|
||||
///
|
||||
|
||||
### 依存関係をインポート { #import-the-dependencies }
|
||||
|
||||
このコードはモジュール `app.routers.items`(ファイル `app/routers/items.py`)内にあります。
|
||||
|
||||
そして依存関係の関数はモジュール `app.dependencies`(ファイル `app/dependencies.py`)から取得する必要があります。
|
||||
|
||||
そこで、依存関係には `..` を使った相対インポートを使います:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *}
|
||||
|
||||
#### 相対インポートの仕組み { #how-relative-imports-work }
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
インポートの仕組みを十分理解している場合は、次の節に進んでください。
|
||||
|
||||
///
|
||||
|
||||
ドット1つ `.` を使うと、次のような意味になります:
|
||||
|
||||
```Python
|
||||
from .dependencies import get_token_header
|
||||
```
|
||||
|
||||
意味:
|
||||
|
||||
* このモジュール(`app/routers/items.py`)が存在する同じパッケージ(ディレクトリ `app/routers/`)から開始し...
|
||||
* モジュール `dependencies`(仮想的には `app/routers/dependencies.py`)を探し...
|
||||
* そこから関数 `get_token_header` をインポートする。
|
||||
|
||||
しかしそのファイルは存在せず、実際の依存関係は `app/dependencies.py` にあります。
|
||||
|
||||
アプリ/ファイル構成がどうなっていたかを思い出してください:
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
|
||||
|
||||
---
|
||||
|
||||
ドット2つ `..` を使うと、次のようになります:
|
||||
|
||||
```Python
|
||||
from ..dependencies import get_token_header
|
||||
```
|
||||
|
||||
意味:
|
||||
|
||||
* このモジュール(`app/routers/items.py`)が存在する同じパッケージ(ディレクトリ `app/routers/`)から開始し...
|
||||
* 親パッケージ(ディレクトリ `app/`)に移動し...
|
||||
* そこでモジュール `dependencies`(ファイル `app/dependencies.py`)を探し...
|
||||
* そこから関数 `get_token_header` をインポートする。
|
||||
|
||||
これは正しく動作します! 🎉
|
||||
|
||||
---
|
||||
|
||||
同様に、ドット3つ `...` を使うと:
|
||||
|
||||
```Python
|
||||
from ...dependencies import get_token_header
|
||||
```
|
||||
|
||||
意味:
|
||||
|
||||
* このモジュール(`app/routers/items.py`)が存在する同じパッケージ(ディレクトリ `app/routers/`)から開始し...
|
||||
* 親パッケージ(ディレクトリ `app/`)に移動し...
|
||||
* さらにその親パッケージに移動しようとします(`app` は最上位なので親パッケージはありません 😱)...
|
||||
* そこでモジュール `dependencies`(ファイル `app/dependencies.py`)を探し...
|
||||
* そこから関数 `get_token_header` をインポートする。
|
||||
|
||||
これは `app/` より上位のパッケージ(独自の `__init__.py` を持つ)を参照することになります。しかしそのようなものはありません。そのため、この例ではエラーになります。🚨
|
||||
|
||||
これで仕組みが分かったので、どれほど複雑でも自分のアプリで相対インポートを使えます。🤓
|
||||
|
||||
### カスタムの `tags`、`responses`、`dependencies` を追加 { #add-some-custom-tags-responses-and-dependencies }
|
||||
|
||||
`APIRouter` に追加済みなので、各 *path operation* に `/items` の prefix や `tags=["items"]` を付けていません。
|
||||
|
||||
しかし、特定の *path operation* に適用される _追加の_ `tags` や、その *path operation* 固有の追加の `responses` を加えることはできます:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
この最後の *path operation* は、`["items", "custom"]` のタグの組み合わせを持ちます。
|
||||
|
||||
またドキュメントには `404` と `403` の両方のレスポンスが表示されます。
|
||||
|
||||
///
|
||||
|
||||
## メインの `FastAPI` { #the-main-fastapi }
|
||||
|
||||
次に、`app/main.py` のモジュールを見ていきます。
|
||||
|
||||
ここでクラス `FastAPI` をインポートして使用します。
|
||||
|
||||
これはすべてをまとめるアプリケーションのメインファイルになります。
|
||||
|
||||
そして大部分のロジックはそれぞれの専用モジュールに置かれるため、メインファイルはかなりシンプルになります。
|
||||
|
||||
### `FastAPI` のインポート { #import-fastapi }
|
||||
|
||||
通常どおり `FastAPI` クラスをインポートして作成します。
|
||||
|
||||
さらに、各 `APIRouter` の依存関係と組み合わされる[グローバル依存関係](dependencies/global-dependencies.md){.internal-link target=_blank}も宣言できます:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *}
|
||||
|
||||
### `APIRouter` のインポート { #import-the-apirouter }
|
||||
|
||||
次に、`APIRouter` を持つ他のサブモジュールをインポートします:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *}
|
||||
|
||||
`app/routers/users.py` と `app/routers/items.py` は同じ Python パッケージ `app` のサブモジュールなので、1つのドット `.` を使った「相対インポート」でインポートできます。
|
||||
|
||||
### インポートの動作 { #how-the-importing-works }
|
||||
|
||||
次の部分:
|
||||
|
||||
```Python
|
||||
from .routers import items, users
|
||||
```
|
||||
|
||||
は次の意味です:
|
||||
|
||||
* このモジュール(`app/main.py`)が存在する同じパッケージ(ディレクトリ `app/`)から開始し...
|
||||
* サブパッケージ `routers`(ディレクトリ `app/routers/`)を探し...
|
||||
* そこからサブモジュール `items`(ファイル `app/routers/items.py`)と `users`(ファイル `app/routers/users.py`)をインポートする...
|
||||
|
||||
モジュール `items` には変数 `router`(`items.router`)があります。これは `app/routers/items.py` で作成した `APIRouter` オブジェクトと同じものです。
|
||||
|
||||
モジュール `users` についても同様です。
|
||||
|
||||
次のようにインポートすることもできます:
|
||||
|
||||
```Python
|
||||
from app.routers import items, users
|
||||
```
|
||||
|
||||
/// info | 情報
|
||||
|
||||
最初のバージョンは「相対インポート」です:
|
||||
|
||||
```Python
|
||||
from .routers import items, users
|
||||
```
|
||||
|
||||
2つ目のバージョンは「絶対インポート」です:
|
||||
|
||||
```Python
|
||||
from app.routers import items, users
|
||||
```
|
||||
|
||||
Python のパッケージとモジュールについて詳しくは、<a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">公式の Python モジュールに関するドキュメント</a>をご覧ください。
|
||||
|
||||
///
|
||||
|
||||
### 名前衝突の回避 { #avoid-name-collisions }
|
||||
|
||||
サブモジュール `items` の変数 `router` だけをインポートするのではなく、サブモジュール自体を直接インポートしています。
|
||||
|
||||
これは、サブモジュール `users` にも `router` という変数があるためです。
|
||||
|
||||
もし次のように続けてインポートした場合:
|
||||
|
||||
```Python
|
||||
from .routers.items import router
|
||||
from .routers.users import router
|
||||
```
|
||||
|
||||
`users` の `router` が `items` のものを上書きしてしまい、同時に両方を使えなくなります。
|
||||
|
||||
同じファイルで両方を使えるようにするため、サブモジュールを直接インポートします:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *}
|
||||
|
||||
### `users` と `items` の `APIRouter` を取り込む { #include-the-apirouters-for-users-and-items }
|
||||
|
||||
では、サブモジュール `users` と `items` から `router` を取り込みます:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *}
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`users.router` は、ファイル `app/routers/users.py` 内の `APIRouter` を含みます。
|
||||
|
||||
`items.router` は、ファイル `app/routers/items.py` 内の `APIRouter` を含みます。
|
||||
|
||||
///
|
||||
|
||||
`app.include_router()` を使って、各 `APIRouter` をメインの `FastAPI` アプリケーションに追加できます。
|
||||
|
||||
そのルーターのすべてのルートがアプリに含まれます。
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
実際には、`APIRouter` で宣言された各 *path operation* ごとに内部的に *path operation* が作成されます。
|
||||
|
||||
つまり裏側では、すべてが同じ単一のアプリであるかのように動作します。
|
||||
|
||||
///
|
||||
|
||||
/// check | 確認
|
||||
|
||||
ルーターを取り込んでもパフォーマンスを心配する必要はありません。
|
||||
|
||||
これは起動時にマイクロ秒で行われます。
|
||||
|
||||
したがってパフォーマンスには影響しません。⚡
|
||||
|
||||
///
|
||||
|
||||
### カスタムの `prefix`、`tags`、`responses`、`dependencies` 付きで `APIRouter` を取り込む { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
|
||||
|
||||
あなたの組織から `app/internal/admin.py` ファイルが提供されたとしましょう。
|
||||
|
||||
そこには、組織が複数プロジェクトで共有している管理用の *path operations* を持つ `APIRouter` が含まれています。
|
||||
|
||||
この例ではとてもシンプルですが、組織内の他プロジェクトと共有しているため、`APIRouter` 自体を直接変更して `prefix`、`dependencies`、`tags` などを追加できないとします:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *}
|
||||
|
||||
それでも、`APIRouter` を取り込む際にカスタムの `prefix` を設定してすべての *path operations* を `/admin` で始めたい、既存の `dependencies` で保護したい、さらに `tags` と `responses` も含めたいとします。
|
||||
|
||||
元の `APIRouter` を変更することなく、`app.include_router()` にこれらのパラメータを渡すことで宣言できます:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *}
|
||||
|
||||
このようにすると、元の `APIRouter` は未変更のままなので、同じ `app/internal/admin.py` ファイルを組織内の他プロジェクトとも引き続き共有できます。
|
||||
|
||||
結果として、このアプリ内では `admin` モジュールの各 *path operation* が次のようになります:
|
||||
|
||||
* prefix は `/admin`
|
||||
* タグは `admin`
|
||||
* 依存関係は `get_token_header`
|
||||
* レスポンスは `418` 🍵
|
||||
|
||||
ただし、これはこのアプリ内のその `APIRouter` にのみ影響し、それを使用する他のコードには影響しません。
|
||||
|
||||
例えば、他のプロジェクトでは同じ `APIRouter` を別の認証方式で使うこともできます。
|
||||
|
||||
### *path operation* を追加 { #include-a-path-operation }
|
||||
|
||||
`FastAPI` アプリに *path operations* を直接追加することもできます。
|
||||
|
||||
ここでは(できることを示すためだけに)追加します 🤷:
|
||||
|
||||
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *}
|
||||
|
||||
そして、`app.include_router()` で追加したほかの *path operations* と一緒に正しく動作します。
|
||||
|
||||
/// info | 非常に技術的な詳細
|
||||
|
||||
注記: これは非常に技術的な詳細で、**読み飛ばして構いません**。
|
||||
|
||||
---
|
||||
|
||||
`APIRouter` は「マウント」されておらず、アプリケーションの他部分から分離されていません。
|
||||
|
||||
これは、それらの *path operations* を OpenAPI スキーマやユーザーインターフェースに含めたいからです。
|
||||
|
||||
完全に分離して独立に「マウント」できないため、*path operations* は直接取り込まれるのではなく「クローン(再作成)」されます。
|
||||
|
||||
///
|
||||
|
||||
## 自動APIドキュメントの確認 { #check-the-automatic-api-docs }
|
||||
|
||||
アプリを実行します:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev app/main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
そして <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> を開きます。
|
||||
|
||||
すべてのサブモジュール由来のパスを含む自動 API ドキュメントが表示され、正しいパス(および prefix)と正しいタグが使われているのが分かります:
|
||||
|
||||
<img src="/img/tutorial/bigger-applications/image01.png">
|
||||
|
||||
## 同じルーターを異なる `prefix` で複数回取り込む { #include-the-same-router-multiple-times-with-different-prefix }
|
||||
|
||||
同じルーターに対して、異なる prefix で `.include_router()` を複数回使うこともできます。
|
||||
|
||||
例えば、同じ API を `/api/v1` と `/api/latest` のように異なる prefix で公開する場合に役立ちます。
|
||||
|
||||
高度な使い方なので不要かもしれませんが、必要な場合に備えて用意されています。
|
||||
|
||||
## `APIRouter` を別の `APIRouter` に取り込む { #include-an-apirouter-in-another }
|
||||
|
||||
`APIRouter` を `FastAPI` アプリケーションに取り込めるのと同じように、`APIRouter` を別の `APIRouter` に取り込むこともできます:
|
||||
|
||||
```Python
|
||||
router.include_router(other_router)
|
||||
```
|
||||
|
||||
`router` を `FastAPI` アプリに取り込む前にこれを実行して、`other_router` の *path operations* も含まれるようにしてください。
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# グローバルな依存関係 { #global-dependencies }
|
||||
|
||||
アプリケーションの種類によっては、アプリ全体に依存関係を追加したい場合があります。
|
||||
|
||||
[`dependencies` を path operation のデコレータに追加](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}できるのと同様に、`FastAPI` アプリケーション自体にも追加できます。
|
||||
|
||||
その場合、アプリケーション内のすべての path operation に適用されます:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py310.py hl[17] *}
|
||||
|
||||
また、[`dependencies` を path operation のデコレータに追加](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}する節で説明した考え方はすべて引き続き当てはまりますが、この場合はアプリ内のすべての path operation に対して適用されます。
|
||||
|
||||
## path operation のグループに対する依存関係 { #dependencies-for-groups-of-path-operations }
|
||||
|
||||
後で、複数ファイルを含む大規模アプリケーションの構成方法([大規模アプリケーション - 複数ファイル](../../tutorial/bigger-applications.md){.internal-link target=_blank})を読むと、path operation のグループに対して 1 つの `dependencies` パラメータを宣言する方法を学びます。
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
# ヘッダーパラメータのモデル { #header-parameter-models }
|
||||
|
||||
関連する**ヘッダーパラメータ**が一式ある場合、それらを宣言するための**Pydantic モデル**を作成できます。
|
||||
|
||||
これにより、モデルを**複数箇所**で**再利用**でき、さらにすべてのパラメータに対するバリデーションやメタデータを一括で宣言できます。😎
|
||||
|
||||
/// note | 備考
|
||||
|
||||
これは FastAPI バージョン `0.115.0` 以降でサポートされています。🤓
|
||||
|
||||
///
|
||||
|
||||
## Pydantic モデルによるヘッダーパラメータ { #header-parameters-with-a-pydantic-model }
|
||||
|
||||
必要な**ヘッダーパラメータ**を**Pydantic モデル**内で宣言し、関数引数ではそのパラメータを `Header` として宣言します:
|
||||
|
||||
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *}
|
||||
|
||||
**FastAPI** はリクエストの**ヘッダー**から**各フィールド**の値を**抽出**し、定義した Pydantic モデルとして渡します。
|
||||
|
||||
## ドキュメントの確認 { #check-the-docs }
|
||||
|
||||
`/docs` のドキュメント UI で必要なヘッダーを確認できます:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/header-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 余分なヘッダーを禁止 { #forbid-extra-headers }
|
||||
|
||||
特殊なユースケース(あまり一般的ではありません)では、受け付けるヘッダーを**制限**したい場合があります。
|
||||
|
||||
Pydantic のモデル設定で `extra` フィールドを `forbid` にして禁止できます:
|
||||
|
||||
{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
クライアントが**余分なヘッダー**を送信しようとすると、**エラー**レスポンスが返されます。
|
||||
|
||||
例えば、クライアントが値 `plumbus` の `tool` ヘッダーを送ろうとすると、ヘッダーパラメータ `tool` は許可されていない旨の**エラー**レスポンスが返されます:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["header", "tool"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "plumbus",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## アンダースコア変換の無効化 { #disable-convert-underscores }
|
||||
|
||||
通常のヘッダーパラメータと同様に、パラメータ名にアンダースコアがある場合は**自動的にハイフンに変換**されます。
|
||||
|
||||
例えば、コード上でヘッダーパラメータ `save_data` を定義すると、想定される HTTP ヘッダーは `save-data` となり、ドキュメント上にもそのように表示されます。
|
||||
|
||||
何らかの理由でこの自動変換を無効化する必要がある場合、ヘッダーパラメータ用の Pydantic モデルでも無効化できます。
|
||||
|
||||
{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *}
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
`convert_underscores` を `False` に設定する前に、アンダースコアを含むヘッダーの使用を禁止している HTTP プロキシやサーバーがあることに留意してください。
|
||||
|
||||
///
|
||||
|
||||
## まとめ { #summary }
|
||||
|
||||
**Pydantic モデル**を使って **FastAPI** で **ヘッダー**を宣言できます。😎
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
# リクエストファイル { #request-files }
|
||||
|
||||
`File` を使って、クライアントがアップロードするファイルを定義できます。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
アップロードされたファイルを受け取るには、まず <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> をインストールします。
|
||||
|
||||
[仮想環境](../virtual-environments.md){.internal-link target=_blank}を作成して有効化し、次のようにインストールしてください:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
アップロードされたファイルは「form data」として送信されるためです。
|
||||
|
||||
///
|
||||
|
||||
## `File` をインポート { #import-file }
|
||||
|
||||
`fastapi` から `File` と `UploadFile` をインポートします:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## `File` パラメータの定義 { #define-file-parameters }
|
||||
|
||||
`Body` や `Form` と同様の方法でファイルのパラメータを作成します:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`File` は `Form` を直接継承したクラスです。
|
||||
|
||||
ただし、`fastapi` から `Query`、`Path`、`File` などをインポートするとき、それらは実際には特殊なクラスを返す関数であることに注意してください。
|
||||
|
||||
///
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
ファイルのボディを宣言するには `File` を使う必要があります。そうしないと、パラメータはクエリパラメータやボディ(JSON)パラメータとして解釈されます。
|
||||
|
||||
///
|
||||
|
||||
ファイルは「form data」としてアップロードされます。
|
||||
|
||||
*path operation 関数* のパラメータの型を `bytes` として宣言すると、**FastAPI** がファイルを読み取り、内容を `bytes` として受け取ります。
|
||||
|
||||
これは内容全体がメモリに保持されることを意味します。小さなファイルには有効です。
|
||||
|
||||
しかし、多くの場合は `UploadFile` を使う方が有利です。
|
||||
|
||||
## `UploadFile` によるファイルパラメータ { #file-parameters-with-uploadfile }
|
||||
|
||||
型を `UploadFile` にしてファイルパラメータを定義します:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *}
|
||||
|
||||
`UploadFile` を使うことには `bytes` に対する次の利点があります:
|
||||
|
||||
- パラメータのデフォルト値に `File()` を使う必要がありません。
|
||||
- 「spooled」ファイルを使います:
|
||||
- 最大サイズまではメモリに保持し、それを超えるとディスクに格納されるファイルです。
|
||||
- そのため、画像・動画・大きなバイナリなどの大きなファイルでも、メモリを使い果たすことなくうまく動作します。
|
||||
- アップロードされたファイルからメタデータを取得できます。
|
||||
- <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> な `async` インターフェースを持ちます。
|
||||
- 実際の Python の <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> オブジェクトを公開しており、file-like オブジェクトを期待する他のライブラリにそのまま渡せます。
|
||||
|
||||
### `UploadFile` { #uploadfile }
|
||||
|
||||
`UploadFile` には次の属性があります:
|
||||
|
||||
- `filename`: アップロード時の元のファイル名を表す `str`(例: `myimage.jpg`)
|
||||
- `content_type`: コンテントタイプ(MIME タイプ / メディアタイプ)を表す `str`(例: `image/jpeg`)
|
||||
- `file`: <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a>(<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> なオブジェクト)。これは実際の Python のファイルオブジェクトで、「file-like」オブジェクトを期待する関数やライブラリに直接渡せます。
|
||||
|
||||
`UploadFile` には次の `async` メソッドがあります。いずれも内部で対応するファイルメソッド(内部の `SpooledTemporaryFile`)を呼び出します。
|
||||
|
||||
- `write(data)`: `data`(`str` または `bytes`)を書き込みます。
|
||||
- `read(size)`: `size`(`int`)バイト/文字を読み込みます。
|
||||
- `seek(offset)`: ファイル内のバイト位置 `offset`(`int`)に移動します。
|
||||
- 例: `await myfile.seek(0)` はファイルの先頭に移動します。
|
||||
- 一度 `await myfile.read()` を実行して、もう一度内容を読みたい場合に特に便利です。
|
||||
- `close()`: ファイルを閉じます。
|
||||
|
||||
これらはすべて `async` メソッドなので、`await` する必要があります。
|
||||
|
||||
例えば、`async` の *path operation 関数* 内では次のように内容を取得できます:
|
||||
|
||||
```Python
|
||||
contents = await myfile.read()
|
||||
```
|
||||
|
||||
通常の `def` の *path operation 関数* 内にいる場合は、`UploadFile.file` に直接アクセスできます。例えば:
|
||||
|
||||
```Python
|
||||
contents = myfile.file.read()
|
||||
```
|
||||
|
||||
/// note | `async` の技術詳細
|
||||
|
||||
`async` メソッドを使うと、**FastAPI** はファイルメソッドをスレッドプールで実行し、その完了を待機します。
|
||||
|
||||
///
|
||||
|
||||
/// note | Starlette の技術詳細
|
||||
|
||||
**FastAPI** の `UploadFile` は **Starlette** の `UploadFile` を直接継承していますが、**Pydantic** や FastAPI の他の部分と互換にするために必要な要素を追加しています。
|
||||
|
||||
///
|
||||
|
||||
## 「Form Data」とは { #what-is-form-data }
|
||||
|
||||
HTML フォーム(`<form></form>`)がサーバーにデータを送る方法は、そのデータに対して通常「特別な」エンコーディングを用い、JSON とは異なります。
|
||||
|
||||
**FastAPI** は JSON ではなく、適切な場所からそのデータを読み取るようにします。
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
ファイルを含まない場合、フォームからのデータは通常「メディアタイプ」`application/x-www-form-urlencoded` でエンコードされます。
|
||||
|
||||
ただしフォームにファイルが含まれる場合は、`multipart/form-data` としてエンコードされます。`File` を使うと、**FastAPI** はボディ内の正しい部分からファイルを取得すべきであると認識します。
|
||||
|
||||
これらのエンコーディングやフォームフィールドの詳細は、<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network - Mozilla 開発者ネットワーク">MDN</abbr> Web Docs の <code>POST</code></a> を参照してください。
|
||||
|
||||
///
|
||||
|
||||
/// warning | 注意
|
||||
|
||||
1 つの *path operation* に複数の `File` および `Form` パラメータを宣言できますが、同時に JSON として受け取ることを期待する `Body` フィールドを宣言することはできません。リクエストのボディは `application/json` ではなく `multipart/form-data` でエンコードされるためです。
|
||||
|
||||
これは **FastAPI** の制限ではなく、HTTP プロトコルの仕様です。
|
||||
|
||||
///
|
||||
|
||||
## 任意のファイルアップロード { #optional-file-upload }
|
||||
|
||||
標準の型アノテーションを使い、デフォルト値を `None` に設定することで、ファイルを任意にできます:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
|
||||
|
||||
## 追加メタデータつきの `UploadFile` { #uploadfile-with-additional-metadata }
|
||||
|
||||
例えば追加のメタデータを設定するために、`UploadFile` と併せて `File()` を使うこともできます:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *}
|
||||
|
||||
## 複数ファイルのアップロード { #multiple-file-uploads }
|
||||
|
||||
同時に複数のファイルをアップロードできます。
|
||||
|
||||
それらは「form data」で送信される同じ「フォームフィールド」に関連付けられます。
|
||||
|
||||
そのためには、`bytes` または `UploadFile` のリストを宣言します:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *}
|
||||
|
||||
宣言どおり、`bytes` または `UploadFile` の `list` を受け取ります。
|
||||
|
||||
/// note | 技術詳細
|
||||
|
||||
`from starlette.responses import HTMLResponse` を使うこともできます。
|
||||
|
||||
**FastAPI** は利便性のため、`starlette.responses` と同じものを `fastapi.responses` として提供しています。ただし、利用可能なレスポンスの多くは Starlette から直接提供されています。
|
||||
|
||||
///
|
||||
|
||||
### 追加メタデータつきの複数ファイルアップロード { #multiple-file-uploads-with-additional-metadata }
|
||||
|
||||
先ほどと同様に、`UploadFile` に対しても `File()` を使って追加のパラメータを設定できます:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *}
|
||||
|
||||
## まとめ { #recap }
|
||||
|
||||
リクエストでフォームデータとして送信されるアップロードファイルを宣言するには、`File`、`bytes`、`UploadFile` を使います。
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# フォームモデル { #form-models }
|
||||
|
||||
FastAPI では、フォームフィールドを宣言するために Pydantic モデルを使用できます。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
フォームを使うには、まず <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> をインストールします。
|
||||
|
||||
まず [仮想環境](../virtual-environments.md){.internal-link target=_blank} を作成して有効化し、そのうえでインストールしてください。例えば:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
/// note | 備考
|
||||
|
||||
これは FastAPI バージョン `0.113.0` 以降でサポートされています。🤓
|
||||
|
||||
///
|
||||
|
||||
## フォーム用の Pydantic モデル { #pydantic-models-for-forms }
|
||||
|
||||
受け取りたいフィールドを **フォームフィールド** として持つ **Pydantic モデル** を宣言し、パラメータを `Form` として宣言するだけです:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *}
|
||||
|
||||
**FastAPI** はリクエストの **フォームデータ** から **各フィールド** のデータを **抽出** し、定義した Pydantic モデルとして渡します。
|
||||
|
||||
## ドキュメントで確認 { #check-the-docs }
|
||||
|
||||
`/docs` のドキュメント UI で確認できます:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/request-form-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 追加のフォームフィールドを禁止 { #forbid-extra-form-fields }
|
||||
|
||||
一部の特殊なユースケース(おそらくあまり一般的ではありません)では、フォームフィールドを Pydantic モデルで宣言したもののみに**制限**し、**追加**のフィールドを**禁止**したい場合があります。
|
||||
|
||||
/// note | 備考
|
||||
|
||||
これは FastAPI バージョン `0.114.0` 以降でサポートされています。🤓
|
||||
|
||||
///
|
||||
|
||||
Pydantic のモデル設定で、`extra` フィールドを `forbid` にできます:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *}
|
||||
|
||||
クライアントが余分なデータを送信しようとすると、**エラー**のレスポンスを受け取ります。
|
||||
|
||||
例えば、クライアントが次のフォームフィールドを送ろうとした場合:
|
||||
|
||||
- `username`: `Rick`
|
||||
- `password`: `Portal Gun`
|
||||
- `extra`: `Mr. Poopybutthole`
|
||||
|
||||
フィールド `extra` は許可されていない旨のエラーレスポンスが返されます:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"type": "extra_forbidden",
|
||||
"loc": ["body", "extra"],
|
||||
"msg": "Extra inputs are not permitted",
|
||||
"input": "Mr. Poopybutthole"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## まとめ { #summary }
|
||||
|
||||
FastAPI でフォームフィールドを宣言するために Pydantic モデルを使用できます。😎
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
# パスワードとBearerによるシンプルなOAuth2 { #simple-oauth2-with-password-and-bearer }
|
||||
|
||||
前章から発展させて、完全なセキュリティフローに必要な不足部分を追加していきます。
|
||||
|
||||
## `username` と `password` を取得する { #get-the-username-and-password }
|
||||
|
||||
`username` と `password` を取得するために **FastAPI** のセキュリティユーティリティを使います。
|
||||
|
||||
OAuth2 では、「password flow」(ここで使用するフロー)を使う場合、クライアント/ユーザーはフォームデータとして `username` と `password` フィールドを送信する必要があります。
|
||||
|
||||
しかも、フィールド名はこの通りでなければなりません。つまり、`user-name` や `email` では動作しません。
|
||||
|
||||
ただし、フロントエンドで最終ユーザーにどう表示するかは自由です。
|
||||
|
||||
また、データベースのモデルでは任意の別名を使って構いません。
|
||||
|
||||
しかし、ログイン用の path operation では、仕様との互換性を保つ(たとえば組み込みのAPIドキュメントシステムを使えるようにする)ために、これらの名前を使う必要があります。
|
||||
|
||||
また、仕様では `username` と `password` はフォームデータとして送らなければならない(つまり、ここではJSONは使わない)ことも定められています。
|
||||
|
||||
### `scope` { #scope }
|
||||
|
||||
仕様では、クライアントは追加のフォームフィールド「`scope`」を送ることができるとも書かれています。
|
||||
|
||||
フォームフィールド名は `scope`(単数形)ですが、実態はスペース区切りの「スコープ」文字列を並べた長い文字列です。
|
||||
|
||||
各「スコープ」は(スペースを含まない)単なる文字列です。
|
||||
|
||||
通常、特定のセキュリティ権限を宣言するために使われます。例えば:
|
||||
|
||||
- `users:read` や `users:write` はよくある例です。
|
||||
- `instagram_basic` は Facebook / Instagram で使われます。
|
||||
- `https://www.googleapis.com/auth/drive` は Google で使われます。
|
||||
|
||||
/// info | 情報
|
||||
|
||||
OAuth2 における「スコープ」は、要求される特定の権限を表す単なる文字列です。
|
||||
|
||||
`:` のような他の文字を含んでいても、URL であっても構いません。
|
||||
|
||||
それらの詳細は実装依存です。
|
||||
|
||||
OAuth2 にとっては単なる文字列です。
|
||||
|
||||
///
|
||||
|
||||
## `username` と `password` を取得するコード { #code-to-get-the-username-and-password }
|
||||
|
||||
では、これを処理するために **FastAPI** が提供するユーティリティを使いましょう。
|
||||
|
||||
### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }
|
||||
|
||||
まず、`OAuth2PasswordRequestForm` をインポートし、`/token` の path operation に `Depends` で依存関係として使います:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}
|
||||
|
||||
`OAuth2PasswordRequestForm` は次のフォームボディを宣言するクラス依存関係です:
|
||||
|
||||
- `username`
|
||||
- `password`
|
||||
- スペース区切りの文字列で構成される、オプションの `scope` フィールド
|
||||
- オプションの `grant_type`
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
OAuth2 の仕様では、固定値 `password` を持つフィールド `grant_type` が実際には必須ですが、`OAuth2PasswordRequestForm` はそれを強制しません。
|
||||
|
||||
強制したい場合は、`OAuth2PasswordRequestForm` の代わりに `OAuth2PasswordRequestFormStrict` を使ってください。
|
||||
|
||||
///
|
||||
|
||||
- オプションの `client_id`(この例では不要)
|
||||
- オプションの `client_secret`(この例では不要)
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`OAuth2PasswordRequestForm` は、`OAuth2PasswordBearer` のように **FastAPI** にとって特別なクラスではありません。
|
||||
|
||||
`OAuth2PasswordBearer` は **FastAPI** にセキュリティスキームであることを認識させます。そのため OpenAPI にそのように追加されます。
|
||||
|
||||
一方、`OAuth2PasswordRequestForm` は、あなた自身でも書けるような単なるクラス依存関係であり、直接 `Form` パラメータを宣言することもできます。
|
||||
|
||||
ただし一般的なユースケースなので、簡単に使えるよう **FastAPI** が直接提供しています。
|
||||
|
||||
///
|
||||
|
||||
### フォームデータの利用 { #use-the-form-data }
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
依存関係クラス `OAuth2PasswordRequestForm` のインスタンスは、スペース区切りの長い文字列を持つ `scope` 属性は持ちません。代わりに、送られてきた各スコープの実際の文字列リストを格納する `scopes` 属性を持ちます。
|
||||
|
||||
この例では `scopes` は使いませんが、必要ならその機能が利用できます。
|
||||
|
||||
///
|
||||
|
||||
次に、フォームフィールドの `username` を使って(疑似の)データベースからユーザーデータを取得します。
|
||||
|
||||
そのユーザーが存在しない場合は、「Incorrect username or password」というエラーを返します。
|
||||
|
||||
エラーには `HTTPException` 例外を使います:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
|
||||
|
||||
### パスワードのチェック { #check-the-password }
|
||||
|
||||
この時点でデータベースからユーザーデータは取得できましたが、まだパスワードを確認していません。
|
||||
|
||||
まず、そのデータを Pydantic の `UserInDB` モデルに入れます。
|
||||
|
||||
プレーンテキストのパスワードを保存してはいけないので、(疑似の)パスワードハッシュ化システムを使います。
|
||||
|
||||
パスワードが一致しなければ、同じエラーを返します。
|
||||
|
||||
#### パスワードハッシュ化 { #password-hashing }
|
||||
|
||||
「ハッシュ化」とは、ある内容(ここではパスワード)を、乱雑に見えるバイト列(単なる文字列)に変換することを指します。
|
||||
|
||||
まったく同じ内容(まったく同じパスワード)を渡すと、毎回まったく同じ乱雑な文字列が得られます。
|
||||
|
||||
しかし、その乱雑な文字列から元のパスワードに戻すことはできません。
|
||||
|
||||
##### なぜパスワードをハッシュ化するのか { #why-use-password-hashing }
|
||||
|
||||
もしデータベースが盗まれても、盗んだ側が手にするのはユーザーのプレーンテキストのパスワードではなく、ハッシュだけです。
|
||||
|
||||
したがって、盗んだ側は同じパスワードを別のシステムで試すことができません(多くのユーザーがあらゆる場所で同じパスワードを使っているため、これは危険になり得ます)。
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
|
||||
|
||||
#### `**user_dict` について { #about-user-dict }
|
||||
|
||||
`UserInDB(**user_dict)` は次を意味します:
|
||||
|
||||
`user_dict` のキーと値を、そのままキーワード引数として渡します。つまり次と同等です:
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
username = user_dict["username"],
|
||||
email = user_dict["email"],
|
||||
full_name = user_dict["full_name"],
|
||||
disabled = user_dict["disabled"],
|
||||
hashed_password = user_dict["hashed_password"],
|
||||
)
|
||||
```
|
||||
|
||||
/// info | 情報
|
||||
|
||||
`**user_dict` のより完全な解説は、[**追加モデル**のドキュメント](../extra-models.md#about-user-in-dict){.internal-link target=_blank}を参照してください。
|
||||
|
||||
///
|
||||
|
||||
## トークンを返す { #return-the-token }
|
||||
|
||||
`token` エンドポイントのレスポンスは JSON オブジェクトでなければなりません。
|
||||
|
||||
`token_type` を含める必要があります。ここでは「Bearer」トークンを使うので、トークンタイプは「`bearer`」です。
|
||||
|
||||
そして `access_token` を含め、その中にアクセストークンの文字列を入れます。
|
||||
|
||||
この単純な例では、完全に安全ではありませんが、トークンとして同じ `username` をそのまま返します。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
次の章では、パスワードハッシュ化と <abbr title="JSON Web Tokens - JSON Web Token">JWT</abbr> トークンを使った本当に安全な実装を見ます。
|
||||
|
||||
しかし今は、必要な特定の詳細に集中しましょう。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
仕様に従うと、この例と同じく `access_token` と `token_type` を含む JSON を返す必要があります。
|
||||
|
||||
これはあなた自身のコードで実装する必要があり、これらのJSONキーを使っていることを確認してください。
|
||||
|
||||
仕様に準拠するために、あなた自身が正しく覚えて実装すべきことは、ほぼこれだけです。
|
||||
|
||||
それ以外は **FastAPI** が面倒を見てくれます。
|
||||
|
||||
///
|
||||
|
||||
## 依存関係の更新 { #update-the-dependencies }
|
||||
|
||||
ここで依存関係を更新します。
|
||||
|
||||
アクティブなユーザーの場合にのみ `current_user` を取得したいとします。
|
||||
|
||||
そこで、`get_current_user` を依存関係として利用する追加の依存関係 `get_current_active_user` を作成します。
|
||||
|
||||
これら2つの依存関係は、ユーザーが存在しない、または非アクティブである場合に、HTTPエラーを返すだけです。
|
||||
|
||||
したがって、エンドポイントでは、ユーザーが存在し、正しく認証され、かつアクティブである場合にのみ、ユーザーを取得します:
|
||||
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
|
||||
|
||||
/// info | 情報
|
||||
|
||||
ここで返している値が `Bearer` の追加ヘッダー `WWW-Authenticate` も仕様の一部です。
|
||||
|
||||
HTTP(エラー)ステータスコード 401「UNAUTHORIZED」は、`WWW-Authenticate` ヘッダーも返すことになっています。
|
||||
|
||||
ベアラートークン(今回のケース)の場合、そのヘッダーの値は `Bearer` であるべきです。
|
||||
|
||||
実際のところ、この追加ヘッダーを省略しても動作はします。
|
||||
|
||||
しかし、仕様に準拠するためにここでは付与しています。
|
||||
|
||||
また、(今または将来)それを想定して利用するツールがあるかもしれず、あなたやユーザーにとって有用になる可能性があります。
|
||||
|
||||
これが標準の利点です…。
|
||||
|
||||
///
|
||||
|
||||
## 動作確認 { #see-it-in-action }
|
||||
|
||||
インタラクティブドキュメントを開きます: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
|
||||
|
||||
### 認証 { #authenticate }
|
||||
|
||||
「Authorize」ボタンをクリックします。
|
||||
|
||||
次の認証情報を使います:
|
||||
|
||||
User: `johndoe`
|
||||
|
||||
Password: `secret`
|
||||
|
||||
<img src="/img/tutorial/security/image04.png">
|
||||
|
||||
システムで認証されると、次のように表示されます:
|
||||
|
||||
<img src="/img/tutorial/security/image05.png">
|
||||
|
||||
### 自分のユーザーデータを取得 { #get-your-own-user-data }
|
||||
|
||||
`GET` の path `/users/me` を使います。
|
||||
|
||||
次のようなユーザーデータが取得できます:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"username": "johndoe",
|
||||
"email": "johndoe@example.com",
|
||||
"full_name": "John Doe",
|
||||
"disabled": false,
|
||||
"hashed_password": "fakehashedsecret"
|
||||
}
|
||||
```
|
||||
|
||||
<img src="/img/tutorial/security/image06.png">
|
||||
|
||||
錠前アイコンをクリックしてログアウトし、同じ操作を再度試すと、次のような HTTP 401 エラーになります:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Not authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
### 非アクティブユーザー { #inactive-user }
|
||||
|
||||
今度は非アクティブなユーザーで試してみます。次で認証してください:
|
||||
|
||||
User: `alice`
|
||||
|
||||
Password: `secret2`
|
||||
|
||||
そして `GET` の path `/users/me` を使います。
|
||||
|
||||
次のような「Inactive user」エラーになります:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Inactive user"
|
||||
}
|
||||
```
|
||||
|
||||
## まとめ { #recap }
|
||||
|
||||
これで、API のために `username` と `password` に基づく完全なセキュリティシステムを実装するための道具が揃いました。
|
||||
|
||||
これらの道具を使えば、任意のデータベース、任意のユーザー/データモデルと互換性のあるセキュリティシステムを構築できます。
|
||||
|
||||
ただし、実際にはまだ「安全」ではありません。
|
||||
|
||||
次の章では、安全なパスワードハッシュライブラリと <abbr title="JSON Web Tokens - JSON Web Token">JWT</abbr> トークンの使い方を見ていきます。
|
||||
|
|
@ -0,0 +1,357 @@
|
|||
# SQL(リレーショナル)データベース { #sql-relational-databases }
|
||||
|
||||
FastAPI は SQL(リレーショナル)データベースの使用を必須にはしません。必要であれば、任意のデータベースを使用できます。
|
||||
|
||||
ここでは <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a> を使った例を見ていきます。
|
||||
|
||||
SQLModel は <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> と Pydantic の上に構築されています。FastAPI と同じ作者により、SQL データベースを使う必要がある FastAPI アプリに最適になるように作られています。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
他の任意の SQL あるいは NoSQL のデータベースライブラリ(場合によっては <abbr title="Object Relational Mapper - オブジェクト関係マッパー: いくつかのクラスが SQL テーブルを表し、そのインスタンスがそれらのテーブルの行を表すライブラリを指す専門用語">"ORMs"</abbr> と呼ばれます)を使うこともできます。FastAPI は何も強制しません。😎
|
||||
|
||||
///
|
||||
|
||||
SQLModel は SQLAlchemy をベースにしているため、SQLAlchemy がサポートする任意のデータベース(SQLModel からもサポートされます)を簡単に使えます。例えば:
|
||||
|
||||
* PostgreSQL
|
||||
* MySQL
|
||||
* SQLite
|
||||
* Oracle
|
||||
* Microsoft SQL Server など
|
||||
|
||||
この例では、単一ファイルで動作し、Python に統合サポートがあるため、SQLite を使います。つまり、この例をそのままコピーして実行できます。
|
||||
|
||||
本番アプリでは、PostgreSQL のようなデータベースサーバーを使いたくなるかもしれません。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
フロントエンドやその他のツールを含む、FastAPI と PostgreSQL の公式プロジェクトジェネレーターがあります: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
|
||||
|
||||
///
|
||||
|
||||
これはとてもシンプルで短いチュートリアルです。データベースや SQL、より高度な機能について学びたい場合は、<a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel のドキュメント</a>をご覧ください。
|
||||
|
||||
## `SQLModel` のインストール { #install-sqlmodel }
|
||||
|
||||
まずは [仮想環境](../virtual-environments.md){.internal-link target=_blank} を作成・有効化し、`sqlmodel` をインストールします:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install sqlmodel
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 単一モデルでアプリ作成 { #create-the-app-with-a-single-model }
|
||||
|
||||
まずは最も簡単な、単一の SQLModel モデルだけを使うバージョンを作ります。
|
||||
|
||||
後で、下記のとおり複数モデルにしてセキュリティと汎用性を高めます。🤓
|
||||
|
||||
### モデルの作成 { #create-models }
|
||||
|
||||
`SQLModel` をインポートしてデータベースモデルを作成します:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}
|
||||
|
||||
`Hero` クラスは Pydantic モデルによく似ています(実際には内部的に Pydantic モデルでもあります)。
|
||||
|
||||
いくつかの違いがあります:
|
||||
|
||||
* `table=True` は SQLModel に対して「これはテーブルモデルであり、SQL データベースのテーブルを表す。単なるデータモデル(通常の Pydantic クラス)ではない」と伝えます。
|
||||
|
||||
* `Field(primary_key=True)` は `id` が SQL データベースのプライマリキーであることを SQLModel に伝えます(SQL のプライマリキーについては SQLModel ドキュメントを参照してください)。
|
||||
|
||||
注: プライマリキーのフィールドには `int | None` を使っています。これは Python コード内で `id=None` のように「`id` なしでオブジェクトを作成」し、保存時にデータベースが生成することを想定するためです。SQLModel はデータベースが `id` を提供することを理解し、スキーマでは「NULL 不可の `INTEGER` 列」を定義します。詳細は <a href="https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#primary-key-id" class="external-link" target="_blank">SQLModel のプライマリキーに関するドキュメント</a> を参照してください。
|
||||
|
||||
* `Field(index=True)` は、この列に対して SQL のインデックスを作成するよう SQLModel に指示します。これにより、この列でフィルタしてデータを読む場合に検索が高速になります。
|
||||
|
||||
`str` と宣言されたものは、SQL の `TEXT`(データベースによっては `VARCHAR`)型の列になることを SQLModel は理解します。
|
||||
|
||||
### Engine の作成 { #create-an-engine }
|
||||
|
||||
SQLModel の `engine`(内部的には SQLAlchemy の `engine`)は、データベースへの接続を保持します。
|
||||
|
||||
同じデータベースに接続するために、コード全体で 1 つの `engine` オブジェクトを共有します。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}
|
||||
|
||||
`check_same_thread=False` を使うと、FastAPI が異なるスレッドで同じ SQLite データベースを使えるようになります。これは、依存関係などにより 1 つのリクエストが複数スレッドを使う可能性があるため、必要です。
|
||||
|
||||
心配はいりません。このコードの構成では、後で「1 リクエストにつき 1 つの SQLModel セッション」を確実に使うようにします。実際、`check_same_thread` はそれを実現しようとしています。
|
||||
|
||||
### テーブルの作成 { #create-the-tables }
|
||||
|
||||
`SQLModel.metadata.create_all(engine)` を使って、すべてのテーブルモデルのテーブルを作成する関数を追加します。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
|
||||
|
||||
### Session 依存関係の作成 { #create-a-session-dependency }
|
||||
|
||||
`Session` は、メモリ上でオブジェクトを保持して変更を追跡し、`engine` を使ってデータベースと通信します。
|
||||
|
||||
各リクエストごとに新しい `Session` を提供する、`yield` を使った FastAPI の依存関係を作成します。これにより、1 リクエストにつき 1 つのセッションを使うことが保証されます。🤓
|
||||
|
||||
続いて、この依存関係を使うコードを簡潔にするために、`Annotated` による依存関係 `SessionDep` を作成します。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
|
||||
|
||||
### 起動時にテーブルを作成 { #create-database-tables-on-startup }
|
||||
|
||||
アプリケーションの起動時にデータベースのテーブルを作成します。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *}
|
||||
|
||||
ここでは、アプリケーションのスタートアップイベントでテーブルを作成しています。
|
||||
|
||||
本番では、アプリを起動する前にマイグレーションスクリプトを実行するのが一般的でしょう。🤓
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
SQLModel は Alembic をラップしたマイグレーションユーティリティを提供予定ですが、現時点では <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> を直接使えます。
|
||||
|
||||
///
|
||||
|
||||
### Hero の作成 { #create-a-hero }
|
||||
|
||||
各 SQLModel モデルは Pydantic モデルでもあるため、Pydantic モデルと同じように型アノテーションで使えます。
|
||||
|
||||
例えば、`Hero` 型のパラメータを宣言すると、JSON ボディから読み込まれます。
|
||||
|
||||
同様に、関数の戻り値の型として宣言すると、そのデータ形状が自動 API ドキュメントの UI に表示されます。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
|
||||
|
||||
ここでは `SessionDep` 依存関係(`Session`)を使って、新しい `Hero` を `Session` インスタンスに追加し、データベースにコミットし、`hero` のデータをリフレッシュしてから返します。
|
||||
|
||||
### Hero の取得 { #read-heroes }
|
||||
|
||||
`select()` を使ってデータベースから `Hero` を取得できます。結果のページネーションのために `limit` と `offset` を含められます。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
|
||||
|
||||
### 単一の Hero を取得 { #read-one-hero }
|
||||
|
||||
単一の `Hero` を取得できます。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
|
||||
|
||||
### Hero の削除 { #delete-a-hero }
|
||||
|
||||
`Hero` を削除することもできます。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
|
||||
|
||||
### アプリの起動 { #run-the-app }
|
||||
|
||||
アプリを起動します:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
その後 `/docs` の UI にアクセスすると、FastAPI がこれらのモデルを使って API をドキュメント化し、同時にデータのシリアライズとバリデーションにも使っていることがわかります。
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image01.png">
|
||||
</div>
|
||||
|
||||
## 複数モデルでアプリを更新 { #update-the-app-with-multiple-models }
|
||||
|
||||
ここで、少しリファクタリングしてセキュリティと汎用性を高めましょう。
|
||||
|
||||
前のアプリでは、UI 上でクライアントが作成する `Hero` の `id` を自分で決められてしまいます。😱
|
||||
|
||||
それは許可すべきではありません。すでに DB で割り当て済みの `id` を上書きされる可能性があります。`id` の決定はクライアントではなく、バックエンドまたはデータベースが行うべきです。
|
||||
|
||||
さらに、`secret_name` を作っていますが、現状ではそれをどこでも返してしまっています。これではあまり「シークレット」ではありません... 😅
|
||||
|
||||
これらを、いくつかの追加モデルで修正します。ここで SQLModel の真価が発揮されます。✨
|
||||
|
||||
### 複数モデルの作成 { #create-multiple-models }
|
||||
|
||||
SQLModel では、`table=True` のあるモデルクラスがテーブルモデルです。
|
||||
|
||||
`table=True` のないモデルクラスはデータモデルで、実体は(小さな機能がいくつか追加された)Pydantic モデルです。🤓
|
||||
|
||||
SQLModel では継承を使って、あらゆるケースでフィールドの重複を避けられます。
|
||||
|
||||
#### `HeroBase` - ベースクラス { #herobase-the-base-class }
|
||||
|
||||
まず、すべてのモデルで共有されるフィールドを持つ `HeroBase` モデルを作ります:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}
|
||||
|
||||
#### `Hero` - テーブルモデル { #hero-the-table-model }
|
||||
|
||||
次に、実際のテーブルモデルである `Hero` を作ります。他のモデルには常に含まれない追加フィールドを持ちます:
|
||||
|
||||
* `id`
|
||||
* `secret_name`
|
||||
|
||||
`Hero` は `HeroBase` を継承しているため、`HeroBase` で宣言されたフィールドも持ちます。つまり、`Hero` の全フィールドは次のとおりです:
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}
|
||||
|
||||
#### `HeroPublic` - 公開用データモデル { #heropublic-the-public-data-model }
|
||||
|
||||
次に、API のクライアントに返す `HeroPublic` モデルを作ります。
|
||||
|
||||
これは `HeroBase` と同じフィールドを持つため、`secret_name` は含みません。
|
||||
|
||||
これでヒーローの正体は守られます!🥷
|
||||
|
||||
また、`id: int` を再宣言します。これにより、API クライアントとの間で「常に `id` が存在し、`int` である(`None` にはならない)」という契約を結びます。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
戻り値のモデルで、値が常に存在し常に `int`(`None` ではない)であることを保証すると、API クライアント側のコードははるかにシンプルに書けます。
|
||||
|
||||
加えて、自動生成クライアントのインターフェースも簡潔になり、あなたの API とやり取りする開発者体験が向上します。😎
|
||||
|
||||
///
|
||||
|
||||
`HeroPublic` のフィールドは `HeroBase` と同じで、`id` は `int`(`None` ではない)として宣言されます:
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
* `age`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}
|
||||
|
||||
#### `HeroCreate` - 作成用データモデル { #herocreate-the-data-model-to-create-a-hero }
|
||||
|
||||
次に、クライアントからのデータをバリデートする `HeroCreate` モデルを作ります。
|
||||
|
||||
これは `HeroBase` と同じフィールドに加え、`secret_name` も持ちます。
|
||||
|
||||
これで、クライアントが新しいヒーローを作成する際に `secret_name` を送信し、データベースに保存されますが、そのシークレット名は API ではクライアントに返されません。
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
これはパスワードを扱う際の方法と同じです。受け取りますが、API では返しません。
|
||||
|
||||
また、保存前にパスワードの値はハッシュ化し、平文のまま保存しないでください。
|
||||
|
||||
///
|
||||
|
||||
`HeroCreate` のフィールド:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}
|
||||
|
||||
#### `HeroUpdate` - 更新用データモデル { #heroupdate-the-data-model-to-update-a-hero }
|
||||
|
||||
前のバージョンのアプリにはヒーローを更新する方法がありませんでしたが、複数モデルを使えば可能です。🎉
|
||||
|
||||
`HeroUpdate` データモデルは少し特殊で、新しいヒーローを作成するのに必要なフィールドと同じフィールドをすべて持ちますが、すべてのフィールドがオプショナル(デフォルト値を持つ)です。これにより、更新時には変更したいフィールドだけを送れます。
|
||||
|
||||
すべてのフィールドの型が実質的に変わる(`None` を含み、デフォルト値が `None` になる)ため、フィールドは再宣言する必要があります。
|
||||
|
||||
すべてのフィールドを再宣言するので、厳密には `HeroBase` を継承する必要はありません。一貫性のためにここでは継承していますが、必須ではありません。好みの問題です。🤷
|
||||
|
||||
`HeroUpdate` のフィールド:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}
|
||||
|
||||
### `HeroCreate` で作成し `HeroPublic` を返す { #create-with-herocreate-and-return-a-heropublic }
|
||||
|
||||
複数モデルが用意できたので、それらを使うようにアプリの部分を更新します。
|
||||
|
||||
リクエストでは `HeroCreate` データモデルを受け取り、そこから `Hero` テーブルモデルを作成します。
|
||||
|
||||
この新しいテーブルモデル `Hero` は、クライアントから送られたフィールドを持ち、データベースによって生成された `id` も持ちます。
|
||||
|
||||
関数からはこのテーブルモデル `Hero` をそのまま返します。しかし `response_model` に `HeroPublic` データモデルを指定しているため、FastAPI が `HeroPublic` を使ってデータをバリデート・シリアライズします。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}
|
||||
|
||||
/// tip | 豆知識
|
||||
|
||||
今回は返却値の型アノテーション `-> HeroPublic` の代わりに `response_model=HeroPublic` を使います。返している値は実際には `HeroPublic` ではないためです。
|
||||
|
||||
もし `-> HeroPublic` と宣言すると、エディタや Linter は(正しく)「`HeroPublic` ではなく `Hero` を返している」と警告します。
|
||||
|
||||
`response_model` に指定することで、型アノテーションやエディタ等の補助を崩さずに、FastAPI にシリアライズの仕事を任せられます。
|
||||
|
||||
///
|
||||
|
||||
### `HeroPublic` で Hero を取得 { #read-heroes-with-heropublic }
|
||||
|
||||
前と同様に `Hero` を取得できます。再び `response_model=list[HeroPublic]` を使って、データが正しくバリデート・シリアライズされることを保証します。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
|
||||
|
||||
### `HeroPublic` で単一の Hero を取得 { #read-one-hero-with-heropublic }
|
||||
|
||||
単一のヒーローを取得します:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
|
||||
|
||||
### `HeroUpdate` で Hero を更新 { #update-a-hero-with-heroupdate }
|
||||
|
||||
ヒーローを更新できます。ここでは HTTP の `PATCH` を使います。
|
||||
|
||||
コードでは、クライアントが送ったデータのみ(デフォルト値として入ってくる値は除外)を持つ `dict` を取得します。これには `exclude_unset=True` を使います。これが主なコツです。🪄
|
||||
|
||||
その後、`hero_db.sqlmodel_update(hero_data)` を使って、`hero_db` を `hero_data` の内容で更新します。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}
|
||||
|
||||
### 再度 Hero を削除 { #delete-a-hero-again }
|
||||
|
||||
ヒーローの削除はほとんど変わりません。
|
||||
|
||||
ここはリファクタリング欲求を満たさないままにしておきます。😅
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}
|
||||
|
||||
### アプリの再起動 { #run-the-app-again }
|
||||
|
||||
アプリを再度起動します:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
`/docs` の API UI に行くと、内容が更新されており、ヒーロー作成時にクライアントから `id` を受け取ることは期待されていない、などが確認できます。
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image02.png">
|
||||
</div>
|
||||
|
||||
## まとめ { #recap }
|
||||
|
||||
<a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a> を使って SQL データベースとやり取りし、データモデルとテーブルモデルでコードを簡潔にできます。
|
||||
|
||||
さらに多くを学ぶには SQLModel のドキュメントをご覧ください。<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">FastAPI と SQLModel を使うチュートリアル</a> もあります。🚀
|
||||
Loading…
Reference in New Issue