コンテンツへスキップ

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

デフォルトでは、FastAPIJSONResponseを使用してレスポンスを返します。

直接レスポンスを返すで示したように、Responseを直接返すことでこれをオーバーライドできます。

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

しかし、response_classパラメータを使用して、パス操作デコレータで、使用したいResponse(例:任意のResponseサブクラス)を宣言することもできます。

パス操作関数から返されるコンテンツは、そのResponseの中に配置されます。

そして、そのResponseJSONResponseUJSONResponseの場合のようにJSONメディアタイプ(application/json)を持つ場合、返されるデータは、パス操作デコレータで宣言したPydanticのresponse_modelによって自動的に変換(およびフィルタリング)されます。

注意

メディアタイプのないレスポンスクラスを使用すると、FastAPIはレスポンスにコンテンツがないことを期待するため、生成されたOpenAPIドキュメントにレスポンス形式を記述しません。

ORJSONResponseを使用する

例えば、パフォーマンスを追求している場合は、orjsonをインストールして使用し、レスポンスをORJSONResponseに設定できます。

使用したいResponseクラス(サブクラス)をインポートし、パス操作デコレータで宣言します。

大きなレスポンスの場合、辞書を返すよりもResponseを直接返す方がはるかに高速です。

これは、デフォルトではFastAPIが各項目を検査し、チュートリアルで説明されているJSON互換エンコーダと同じものを使用して、JSONとしてシリアル化可能であることを確認するためです。これにより、データベースモデルなどの任意のオブジェクトを返すことができます。

しかし、返されるコンテンツがJSONでシリアル化可能であると確信している場合は、コンテンツをレスポンスクラスに直接渡すことで、FastAPIがjsonable_encoderを介してコンテンツをレスポンスクラスに渡す前の追加のオーバーヘッドを回避できます。

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI()


@app.get("/items/", response_class=ORJSONResponse)
async def read_items():
    return ORJSONResponse([{"item_id": "Foo"}])

情報

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

この場合、HTTPヘッダーContent-Typeapplication/jsonに設定されます。

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

ヒント

ORJSONResponseはFastAPIでのみ利用可能で、Starletteでは利用できません。

HTMLレスポンス

FastAPIから直接HTMLレスポンスを返すには、HTMLResponseを使用します。

  • HTMLResponseをインポートします。
  • パス操作デコレータのパラメータresponse_classとしてHTMLResponseを渡します。
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-Typetext/htmlに設定されます。

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

Responseを返す

直接レスポンスを返すで示したように、パス操作でレスポンスを直接オーバーライドすることもできます。

上記の例と同じで、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)

警告

パス操作関数によって直接返されたResponseは、OpenAPIにドキュメント化されず(例えば、Content-Typeはドキュメント化されません)、自動対話型ドキュメントにも表示されません。

情報

もちろん、実際のContent-Typeヘッダー、ステータスコードなどは、返されたResponseオブジェクトから取得されます。

OpenAPIでドキュメント化し、Responseを上書きする

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

response_classはOpenAPIのパス操作をドキュメント化するためにのみ使用され、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_classHTMLResponseも渡しているので、FastAPIはOpenAPIとインタラクティブなドキュメントでHTMLをtext/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でデフォルトで使用されるレスポンスです。

ORJSONResponse

上記で読んだように、orjsonを使用した高速な代替JSONレスポンス。

情報

これは、例えばpip install orjsonorjsonをインストールする必要があります。

UJSONResponse

ujsonを使用した代替JSONレスポンス。

情報

これは、例えばpip install ujsonujsonをインストールする必要があります。

警告

ujsonは、いくつかのエッジケースの処理において、Pythonの組み込み実装よりも注意深くありません。

from fastapi import FastAPI
from fastapi.responses import UJSONResponse

app = FastAPI()


@app.get("/items/", response_class=UJSONResponse)
async def read_items():
    return [{"item_id": "Foo"}]

ヒント

ORJSONResponseの方が高速な代替手段である可能性があります。

RedirectResponse

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

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

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


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

または、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.dokyumento.jp"

そうすると、パス操作関数から直接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

非同期ジェネレータまたは通常のジェネレータ/イテレータを受け取り、レスポンスボディをストリーミングします。

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"


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

ファイルライクオブジェクトとStreamingResponseを使用する

ファイルライクオブジェクト(例:open()によって返されるオブジェクト)がある場合、そのファイルライクオブジェクトを反復処理するジェネレータ関数を作成できます。

そうすることで、最初にすべてをメモリに読み込む必要がなく、そのジェネレータ関数をStreamingResponseに渡し、それを返すことができます。

これには、クラウドストレージ、ビデオ処理などと対話するための多くのライブラリが含まれます。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

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


@app.get("/")
def main():
    def iterfile():  # (1)
        with open(some_file_path, mode="rb") as file_like:  # (2)
            yield from file_like  # (3)

    return StreamingResponse(iterfile(), media_type="video/mp4")
  1. これはジェネレータ関数です。内部にyieldステートメントが含まれているため「ジェネレータ関数」です。
  2. withブロックを使用することで、ジェネレータ関数が完了した後(つまり、レスポンスの送信が完了した後)、ファイルライクオブジェクトが閉じられることを保証します。
  3. このyield fromは、file_likeというものを反復処理するように関数に指示します。そして、反復処理された各部分について、その部分がこのジェネレータ関数(iterfile)から来ているかのように生成します。

    つまり、内部的に「生成」の作業を他のものに転送するジェネレータ関数です。

    このようにすることで、withブロックに入れることができ、その方法で、ファイルライクオブジェクトが終了後に閉じられることを保証します。

ヒント

ここで、標準のopen()asyncawaitをサポートしないため、パス操作を通常のdefで宣言していることに注意してください。

FileResponse

ファイルを非同期でストリーミングしてレスポンスとして返します。

他のレスポンスタイプとは異なる引数セットを受け取ってインスタンス化します。

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

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

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

この場合、パス操作関数から直接ファイルパスを返すことができます。

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

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

例えば、orjsonを使用したいが、付属のORJSONResponseクラスでは使用されていないカスタム設定を使いたいとします。

インデントされフォーマットされた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をフォーマットするよりも、これを利用するはるかに良い方法が見つかるでしょう。😉

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

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

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

以下の例では、FastAPIは、すべてのパス操作で、JSONResponseの代わりにORJSONResponseをデフォルトで使用します。

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)


@app.get("/items/")
async def read_items():
    return [{"item_id": "Foo"}]

ヒント

これまでと同様に、パス操作response_classをオーバーライドすることは可能です。

追加のドキュメント

また、responsesを使用して、OpenAPIでメディアタイプやその他の多くの詳細を宣言することもできます。OpenAPIにおける追加のレスポンス