|
| 1 | +# データのストリーミング { #stream-data } |
| 2 | + |
| 3 | +JSON として構造化できるデータをストリームしたい場合は、[JSON Lines をストリームする](../tutorial/stream-json-lines.md) を参照してください。 |
| 4 | + |
| 5 | +しかし、純粋なバイナリデータや文字列をストリームしたい場合は、次のようにできます。 |
| 6 | + |
| 7 | +/// info | 情報 |
| 8 | + |
| 9 | +FastAPI 0.134.0 で追加されました。 |
| 10 | + |
| 11 | +/// |
| 12 | + |
| 13 | +## ユースケース { #use-cases } |
| 14 | + |
| 15 | +例えば、AI LLM サービスの出力をそのまま、純粋な文字列としてストリームしたい場合に使えます。 |
| 16 | + |
| 17 | +メモリに一度に全て読み込むことなく、読み込みながらチャンクごとに送ることで、巨大なバイナリファイルをストリームすることにも使えます。 |
| 18 | + |
| 19 | +同様に、動画や音声をストリームすることもできます。処理しながら生成し、そのまま送信することも可能です。 |
| 20 | + |
| 21 | +## `yield` を使った `StreamingResponse` { #a-streamingresponse-with-yield } |
| 22 | + |
| 23 | +path operation 関数で `response_class=StreamingResponse` を宣言すると、`yield` を使ってデータをチャンクごとに順次送信できます。 |
| 24 | + |
| 25 | +{* ../../docs_src/stream_data/tutorial001_py310.py ln[1:23] hl[20,23] *} |
| 26 | + |
| 27 | +FastAPI は各データチャンクをそのまま `StreamingResponse` に渡し、JSON などに変換しようとはしません。 |
| 28 | + |
| 29 | +### 非 async な path operation 関数 { #non-async-path-operation-functions } |
| 30 | + |
| 31 | +`async` なしの通常の `def` 関数でも同様に `yield` を使えます。 |
| 32 | + |
| 33 | +{* ../../docs_src/stream_data/tutorial001_py310.py ln[26:29] hl[27] *} |
| 34 | + |
| 35 | +### アノテーションなし { #no-annotation } |
| 36 | + |
| 37 | +バイナリデータをストリームする場合、戻り値の型アノテーションを宣言する必要は実際にはありません。 |
| 38 | + |
| 39 | +この場合、FastAPI はデータを Pydantic で JSON 化したり、何らかの方法でシリアライズしようとしないため、型アノテーションはエディタやツール向けの補助にすぎず、FastAPI 自体では使用されません。 |
| 40 | + |
| 41 | +{* ../../docs_src/stream_data/tutorial001_py310.py ln[32:35] hl[33] *} |
| 42 | + |
| 43 | +つまり、`StreamingResponse` では型アノテーションに依存せず、送信したい形式に合わせてバイト列を生成・エンコードする「自由」と「責任」があなたにあります。 🤓 |
| 44 | + |
| 45 | +### バイト列をストリームする { #stream-bytes } |
| 46 | + |
| 47 | +主なユースケースの一つは、文字列ではなく `bytes` をストリームすることです。もちろん可能です。 |
| 48 | + |
| 49 | +{* ../../docs_src/stream_data/tutorial001_py310.py ln[44:47] hl[47] *} |
| 50 | + |
| 51 | +## カスタム `PNGStreamingResponse` { #a-custom-pngstreamingresponse } |
| 52 | + |
| 53 | +上記の例ではバイト列をストリームしましたが、レスポンスに `Content-Type` ヘッダがないため、クライアントは受け取るデータの種類を認識できませんでした。 |
| 54 | + |
| 55 | +`StreamingResponse` を継承したカスタムクラスを作成し、ストリームするデータに応じて `Content-Type` ヘッダを設定できます。 |
| 56 | + |
| 57 | +例えば、`media_type` 属性で `Content-Type` を `image/png` に設定する `PNGStreamingResponse` を作成できます: |
| 58 | + |
| 59 | +{* ../../docs_src/stream_data/tutorial002_py310.py ln[6,19:20] hl[20] *} |
| 60 | + |
| 61 | +その後、path operation 関数で `response_class=PNGStreamingResponse` としてこの新しいクラスを使用できます: |
| 62 | + |
| 63 | +{* ../../docs_src/stream_data/tutorial002_py310.py ln[23:27] hl[23] *} |
| 64 | + |
| 65 | +### ファイルを模擬する { #simulate-a-file } |
| 66 | + |
| 67 | +この例では `io.BytesIO` でファイルを模擬しています。これはメモリ上だけに存在するファイルライクオブジェクトですが、通常のファイルと同じインターフェースを提供します。 |
| 68 | + |
| 69 | +例えば、ファイルと同様にイテレートして内容を読み出せます。 |
| 70 | + |
| 71 | +{* ../../docs_src/stream_data/tutorial002_py310.py ln[1:27] hl[3,12:13,25] *} |
| 72 | + |
| 73 | +/// note | 技術詳細 |
| 74 | + |
| 75 | +他の2つの変数 `image_base64` と `binary_image` は、画像を Base64 でエンコードし、それを `bytes` に変換してから `io.BytesIO` に渡したものです。 |
| 76 | + |
| 77 | +この例では1つのファイル内に完結させ、コピーしてそのまま実行できるようにするためだけのものです。 🥚 |
| 78 | + |
| 79 | +/// |
| 80 | + |
| 81 | +`with` ブロックを使うことで、ジェネレータ関数(`yield` を持つ関数)が終了した後、つまりレスポンス送信が完了した後に、そのファイルライクオブジェクトが確実にクローズされます。 |
| 82 | + |
| 83 | +この例では `io.BytesIO` によるメモリ内の疑似ファイルなので重要度は高くありませんが、実ファイルの場合は処理後に確実にクローズすることが重要です。 |
| 84 | + |
| 85 | +### ファイルと非同期 { #files-and-async } |
| 86 | + |
| 87 | +多くの場合、ファイルライクオブジェクトはデフォルトでは async/await と互換性がありません。 |
| 88 | + |
| 89 | +例えば、`await file.read()` や `async for chunk in file` のような操作は提供されていません。 |
| 90 | + |
| 91 | +また、多くの場合、ディスクやネットワークから読み出すため、読み取りはブロッキング(イベントループをブロックし得る)処理になります。 |
| 92 | + |
| 93 | +/// info | 情報 |
| 94 | + |
| 95 | +上記の例は例外で、`io.BytesIO` は既にメモリ上にあるため、読み取りが何かをブロックすることはありません。 |
| 96 | + |
| 97 | +しかし多くの場合、ファイルやファイルライクオブジェクトの読み取りはブロッキングになります。 |
| 98 | + |
| 99 | +/// |
| 100 | + |
| 101 | +イベントループのブロッキングを避けるには、path operation 関数を `async def` ではなく通常の `def` で宣言してください。そうすると FastAPI はその関数をスレッドプールワーカー上で実行し、メインループのブロッキングを避けます。 |
| 102 | + |
| 103 | +{* ../../docs_src/stream_data/tutorial002_py310.py ln[30:34] hl[31] *} |
| 104 | + |
| 105 | +/// tip | 豆知識 |
| 106 | + |
| 107 | +async 関数内からブロッキングなコードを呼び出す必要がある場合、あるいはブロッキングな関数内から async 関数を呼び出す必要がある場合は、FastAPI の兄弟ライブラリである [Asyncer](https://asyncer.tiangolo.com) を利用できます。 |
| 108 | + |
| 109 | +/// |
| 110 | + |
| 111 | +### `yield from` { #yield-from } |
| 112 | + |
| 113 | +ファイルライクオブジェクトのようなものをイテレートして各要素に対して `yield` している場合、`for` ループを省略して、`yield from` で各要素をそのまま送ることもできます。 |
| 114 | + |
| 115 | +これは FastAPI 固有ではなく単なる Python の機能ですが、知っておくと便利な小ワザです。 😎 |
| 116 | + |
| 117 | +{* ../../docs_src/stream_data/tutorial002_py310.py ln[37:40] hl[40] *} |
0 commit comments