FastAPIで、DELETEのときのステータスコードを`204No Content`に変更する

RFC基準と削除操作のステータスコード

RFC 9110では、特に削除操作(DELETEリクエスト)の場合、レスポンスボディが含まれる場合は200 OKを使用し、レスポンスボディがない場合は204 No Contentを使用すべきとされています。

a DELETE method is successfully applied, the origin server SHOULD send a 202 (Accepted) status code if the action will likely succeed but has not yet been enacted, a 204 (No Content) status code if the action has been enacted and no further information is to be supplied, or a 200 (OK) status code if the action has been enacted and the response message includes a representation describing the status.

しかし、FastAPIはリクエストが成功した時は、デフォルトで200 OKステータスコードを返すようになっているので、今回は削除が成功した時に204 No Contentを返せるようにしてみます。
Response class - FastAPI

DELETEエンドポイントの設定

reports = {
    "1": {"title": "Report 1", "description": "This is a report"},
    "2": {"title": "Report 2", "description": "This is another report"},
    "3": {"title": "Report 3", "description": "This is a third report"}
}


@app.get("/reports")
async def get_reports():
    return reports


@app.delete("/reports/{report_id}", status_code=204)
async def delete_report(report_id: str):
    if report_id in reports:
        del reports[report_id]
    else:
        raise HTTPException(status_code=404, detail="Item not found")

エンドポイントの設定時に、status_code=204を指定し、DELETEが出来ていることを確認するため、GETも用意しています。

Swagger UIで確認


status code 204が返ってきました。

実装を確認する

Starlette

# starlette/responses.py
class Response:
    media_type = None
    charset = "utf-8"

    def __init__(
        self,
        content: typing.Any = None,
        status_code: int = 200,
        headers: typing.Optional[typing.Mapping[str, str]] = None,
        media_type: typing.Optional[str] = None,
        background: typing.Optional[BackgroundTask] = None,
    ) -> None:
        self.status_code = status_code
(略)

FastAPI

from starlette.responses import JSONResponse, Response

    async def app(request: Request) -> Response:
        try:
            body: Any = None
            if body_field:
                if is_body_form:
                    body = await request.form()
                    stack = request.scope.get("fastapi_astack")
                    assert isinstance(stack, AsyncExitStack)
                    stack.push_async_callback(body.close)
                else:
                    body_bytes = await request.body()
                    if body_bytes:
                        json_body: Any = Undefined
                        content_type_value = request.headers.get("content-type")
                        if not content_type_value:
                            json_body = await request.json()
                        else:
                            message = email.message.Message()
                            message["content-type"] = content_type_value
                            if message.get_content_maintype() == "application":
                                subtype = message.get_content_subtype()
                                if subtype == "json" or subtype.endswith("+json"):
                                    json_body = await request.json()
                        if json_body != Undefined:
                            body = json_body
                        else:
                            body = body_bytes

    def api_route(
        self,
        path: str,
        *,
        response_model: Any = Default(None),
        status_code: Optional[int] = None,
       (略)
        ),
    ) -> Callable[[DecoratedCallable], DecoratedCallable]:
        def decorator(func: DecoratedCallable) -> DecoratedCallable:
            self.add_api_route(
                path,
                func,
                response_model=response_model,
                status_code=status_code,

def delete(
        self,
        path: str,
        *,
        response_model: Any = Default(None),
        status_code: Optional[int] = None,
(略)

FastAPIは、StarletteからResponseクラスをインポートして、Starletteが提供するレスポンスの生成と管理機能にアクセスし、 async def app(request: Request) -> Responsedef delete(...)で、HTTPリクエストを受け取り、StarletteのResponse型を戻り値として設定して、DELETEレスポンスを返しているようです。

ソースコード

https://github.com/kei-kmj/redirect_slashes_test/pull/1/commits/947c1438d734f48569bd08b2f610c4a68d8a4466

参考