コンテンツにスキップ

カスタムレスポンス - HTML、ストリーム、ファイル、その他

🌐 AI と人間による翻訳

この翻訳は、人間のガイドに基づいて AI によって作成されました。🤝

原文の意図を取り違えていたり、不自然な表現になっている可能性があります。🤖

AI LLM をより適切に誘導するのを手伝う ことで、この翻訳を改善できます。

英語版

デフォルトでは、FastAPI はJSONレスポンスを返します。

レスポンスを直接返すで見たように、Response を直接返してこの挙動を上書きできます。

しかし、Response(または JSONResponse のような任意のサブクラス)を直接返す場合、(response_model を宣言していても)データは自動的に変換されず、ドキュメントも自動生成されません(例えば、生成されるOpenAPIの一部としてHTTPヘッダー Content-Type に特定の「メディアタイプ」を含めるなど)。

一方で、path operation デコレータresponse_class パラメータを使って、使用したい ResponseResponse の任意のサブクラス)を宣言することもできます。

path operation 関数 から返したコンテンツは、その Response に格納されます。

備考

メディアタイプを持たないレスポンスクラスを使用すると、FastAPIはレスポンスにコンテンツがないものと見なします。そのため、生成されるOpenAPIドキュメントにレスポンスフォーマットは記載されません。

JSONレスポンス

FastAPI はデフォルトでJSONレスポンスを返します。

レスポンスモデル を宣言すると、FastAPI は Pydantic を使ってデータをJSONにシリアライズします。

レスポンスモデルを宣言しない場合、FastAPI は JSON Compatible Encoder で説明した jsonable_encoder を使い、その結果を JSONResponse に入れます。

JSONResponse のようにJSONメディアタイプ(application/json)を持つ response_class を宣言した場合、path operation デコレータ に宣言した任意のPydanticの response_model に従って、返すデータは自動的に変換(およびフィルタ)されます。ただし、そのデータは Pydantic でJSONのバイト列にシリアライズされるわけではなく、まず jsonable_encoder で変換された後に JSONResponse クラスへ渡され、Pythonの標準JSONライブラリでバイト列にシリアライズされます。

JSONのパフォーマンス

結論として、最大のパフォーマンスを得たい場合は、レスポンスモデル を使い、path operation デコレータresponse_class は宣言しないでください。

# Code above omitted 👆

@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item

# Code below omitted 👇
👀 Full file preview
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item


@app.get("/items/")
async def read_items() -> list[Item]:
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]

HTMLレスポンス

FastAPI からHTMLを直接返すには、HTMLResponse を使います。

  • HTMLResponse をインポートする
  • path operation デコレータ のパラメータ response_classHTMLResponse を渡す
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """

情報

パラメータ response_class は、レスポンスの「メディアタイプ」を定義するためにも使用されます。

この場合、HTTPヘッダー Content-Type には text/html が設定されます。

そして、OpenAPIにもそのようにドキュメント化されます。

Response を返す

レスポンスを直接返すで見たように、path operation の中でレスポンスを直接返して上書きすることもできます。

上記と同じ例で、HTMLResponse を返すと次のようになります:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/")
async def read_items():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)

注意

path operation 関数 から直接返される Response は、OpenAPIにドキュメント化されず(例えば Content-Type がドキュメント化されない)、自動生成の対話的ドキュメントにも表示されません。

情報

もちろん、実際の Content-Type ヘッダーやステータスコードなどは、返した Response オブジェクトに由来します。

OpenAPIにドキュメント化しつつ Response を上書き

関数の中からレスポンスを上書きしつつ、同時にOpenAPIで「メディアタイプ」をドキュメント化したい場合は、response_class パラメータを使用し、かつ Response オブジェクトを返します。

この場合、response_class はOpenAPIの path operation をドキュメント化するためだけに使われ、実際には返した Response がそのまま使用されます。

HTMLResponse を直接返す

例えば、次のようになります:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


def generate_html_response():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return generate_html_response()

この例では、関数 generate_html_response() はHTMLの str を返すのではなく、すでに Response を生成して返しています。

generate_html_response() の呼び出し結果を返すことで、デフォルトの FastAPI の挙動を上書きする Response をすでに返しています。

しかし、response_class にも HTMLResponse を渡しているため、FastAPI はOpenAPIおよび対話的ドキュメントで、それが text/html のHTMLであると正しくドキュメント化できます:

利用可能なレスポンス

以下は利用可能なレスポンスの一部です。

Response を使って他のものを返したり、カスタムサブクラスを作成することもできます。

技術詳細

from starlette.responses import HTMLResponse を使うこともできます。

FastAPI は開発者の利便性のために、starlette.responses と同じものを fastapi.responses として提供しています。ただし、利用可能なレスポンスの多くはStarletteから直接提供されています。

Response

メインの Response クラスで、他のすべてのレスポンスはこれを継承しています。

直接返すことができます。

以下のパラメータを受け付けます。

  • content - str または bytes
  • status_code - int のHTTPステータスコード
  • headers - 文字列の dict
  • media_type - メディアタイプを示す str。例: "text/html"

FastAPI(実際にはStarlette)は自動的に Content-Length ヘッダーを含めます。また、media_type に基づいた Content-Type ヘッダーを含め、テキストタイプには charset を追加します。

from fastapi import FastAPI, Response

app = FastAPI()


@app.get("/legacy/")
def get_legacy_data():
    data = """<?xml version="1.0"?>
    <shampoo>
    <Header>
        Apply shampoo here.
    </Header>
    <Body>
        You'll have to use soap here.
    </Body>
    </shampoo>
    """
    return Response(content=data, media_type="application/xml")

HTMLResponse

上で読んだように、テキストやバイト列を受け取り、HTMLレスポンスを返します。

PlainTextResponse

テキストやバイト列を受け取り、プレーンテキストのレスポンスを返します。

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()


@app.get("/", response_class=PlainTextResponse)
async def main():
    return "Hello World"

JSONResponse

データを受け取り、application/json としてエンコードされたレスポンスを返します。

これは、上で述べたように FastAPI のデフォルトのレスポンスです。

技術詳細

ただし、レスポンスモデルや返却型を宣言した場合は、それが直接データのJSONシリアライズに使われ、適切なJSONのメディアタイプを持つレスポンスが JSONResponse クラスを使わずに直接返されます。

これが最適なパフォーマンスを得る理想的な方法です。

RedirectResponse

HTTPリダイレクトを返します。デフォルトでは307ステータスコード(Temporary Redirect)を使用します。

RedirectResponse を直接返せます:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/typer")
async def redirect_typer():
    return RedirectResponse("https://typer.tiangolo.com")

または、response_class パラメータで使用できます:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/fastapi", response_class=RedirectResponse)
async def redirect_fastapi():
    return "https://fastapi.tiangolo.com"

その場合、path operation 関数からURLを直接返せます。

この場合に使用される status_code は、RedirectResponse のデフォルトである 307 になります。


さらに、status_code パラメータを response_class パラメータと組み合わせて使うこともできます:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/pydantic", response_class=RedirectResponse, status_code=302)
async def redirect_pydantic():
    return "https://docs.pydantic.dev/"

StreamingResponse

非同期ジェネレータ、または通常のジェネレータ/イテレータ(yield を持つ関数)を受け取り、レスポンスボディをストリームします。

import anyio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


async def fake_video_streamer():
    for i in range(10):
        yield b"some fake video bytes"
        await anyio.sleep(0)


@app.get("/")
async def main():
    return StreamingResponse(fake_video_streamer())

技術詳細

async タスクは await に到達したときにのみキャンセルできます。await がない場合、ジェネレータ(yield を持つ関数)は適切にキャンセルできず、キャンセル要求後も実行が続く可能性があります。

この小さな例では await が不要なため、イベントループにキャンセルを処理する機会を与えるために await anyio.sleep(0) を追加しています。

これは大きなストリームや無限ストリームではさらに重要になります。

豆知識

StreamingResponse を直接返す代わりに、データをストリームする スタイルに従うことをおすすめします。こちらのほうがはるかに便利で、裏側でキャンセル処理も行ってくれます。

JSON Lines をストリームする場合は、JSON Lines をストリームする チュートリアルを参照してください。

FileResponse

ファイルをレスポンスとして非同期にストリームします。

他のレスポンスタイプとは異なる引数セットでインスタンス化します:

  • path - ストリームするファイルのファイルパス
  • headers - 含めたい任意のカスタムヘッダー(辞書)
  • media_type - メディアタイプを示す文字列。未設定の場合、ファイル名やパスから推測されます
  • filename - 設定した場合、レスポンスの Content-Disposition に含まれます

ファイルレスポンスには、適切な Content-LengthLast-ModifiedETag ヘッダーが含まれます。

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/")
async def main():
    return FileResponse(some_file_path)

response_class パラメータを使うこともできます:

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/", response_class=FileResponse)
async def main():
    return some_file_path

この場合、path operation 関数からファイルパスを直接返せます。

カスタムレスポンスクラス

Response を継承して、独自のカスタムレスポンスクラスを作成し、使用できます。

例えば、orjson を特定の設定で使いたいとします。

インデントされた整形済みJSONを返したいので、orjson のオプション orjson.OPT_INDENT_2 を使いたいとします。

CustomORJSONResponse を作成できます。主に必要なのは、bytes を返す Response.render(content) メソッドを作ることです:

from typing import Any

import orjson
from fastapi import FastAPI, Response

app = FastAPI()


class CustomORJSONResponse(Response):
    media_type = "application/json"

    def render(self, content: Any) -> bytes:
        assert orjson is not None, "orjson must be installed"
        return orjson.dumps(content, option=orjson.OPT_INDENT_2)


@app.get("/", response_class=CustomORJSONResponse)
async def main():
    return {"message": "Hello World"}

これまでは次のように返していたものが:

{"message": "Hello World"}

...このレスポンスでは次のように返されます:

{
  "message": "Hello World"
}

もちろん、JSONの整形以外にも、これを活用するもっと良い方法が見つかるはずです。 😉

orjson か レスポンスモデルか

もし求めているのがパフォーマンスであれば、orjson レスポンスを使うより、レスポンスモデル を使うほうが良い場合が多いです。

レスポンスモデルがあると、FastAPI は中間ステップ(他の場合に行われる jsonable_encoder による変換など)を介さずに、Pydantic を使ってデータをJSONにシリアライズします。

内部的には、Pydantic はJSONシリアライズに orjson と同じRust由来の仕組みを用いているため、レスポンスモデルを使うだけで最良のパフォーマンスが得られます。

デフォルトレスポンスクラス

FastAPI クラスのインスタンスや APIRouter を作成する際に、デフォルトで使用するレスポンスクラスを指定できます。

これを定義するパラメータは default_response_class です。

以下の例では、FastAPI はすべての path operation で、JSONの代わりにデフォルトで HTMLResponse を使用します。

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI(default_response_class=HTMLResponse)


@app.get("/items/")
async def read_items():
    return "<h1>Items</h1><p>This is a list of items.</p>"

豆知識

これまでと同様に、path operation ごとに response_class をオーバーライドできます。

その他のドキュメント

OpenAPIでは responses を使ってメディアタイプやその他の詳細を宣言することもできます: OpenAPI の追加レスポンス