コンテンツにスキップ

カスタムレスポンス - HTML、ストリーム、ファイルなど

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

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

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

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

パスオペレーション関数から返される内容は、そのResponseの中に入れられます。

また、JSONResponseUJSONResponseのように、Responseに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とインタラクティブドキュメントで、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ヘッダーを含め、テキストタイプの場合は文字セットを追加します。

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ブロックに配置でき、それによって、終了後にファイルのようなオブジェクトが閉じられるようにします。

ヒント

ここでは、asyncawaitをサポートしない標準のopen()を使用しているため、パスオペレーションは通常のdefで宣言していることに注意してください。

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

この場合、パスオペレーション関数からファイルパスを直接返すことができます。

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

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の追加レスポンス