📑

FastAPI で None の値を null から undefined に変換する

2023/02/04に公開

はじめに

こんにちは。@hayata-yamamotoです。

FastAPI をベースにしたバックエンドを運用開発していくにあたり None の使用を伴う API 定義を行うと、None で指定した値が OpenAPI 定義上では Optional の扱いを受けてしまい、TypeScript 側で見ると undefined と見做されるという問題がありました。

今回は、その OpenAPI 定義上の表記と、FastAPI の実際のレスポンスを整合させるために行った設定をご紹介します。

解決方法

FastAPI で response_model_exclude_none を True で設定すると、 None の値が含まれるキーがレスポンス上に存在しない状況になります。[1]

これは、Pydantic に用意されている dict メソッドに定義されている exclude_none[2] を API のレスポンスに対して実行するメソッドで、レスポンスをシリアライズする際に、None で定義されている値を全て除外してくれます。

api.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Article(BaseModel):
    id: str
    content: str
    category: str | None


@app.get("/articles", response_model=list[Article], response_model_exclude_none=True)
def get_articles() -> list[Article]:
    return [{"id": "test1", "content": "test", "category": None}]

具体的にどんな問題があったのか?

弊社では、FastAPI を用いてバックエンドシステムを運用し、OpenAPI 定義をやりとりすることでクライアントサイドが常に最新の API 情報を使える状態を作っています。

イメージ

しかし、以下のような API を定義すると、レスポンスで返却される category の値は実際には null であるにもかかわらず、OpenAPI 定義上の category は Optional な値となり、クライアントサイドから見ると undefined が返却されるように見えてしまいます。Python の型定義では、OptionalUnion[X, None]X | None に相当する型定義であり、バックエンドとしては null になるのが正解な挙動ではあるのですが、クライアントサイドからみると OpenAPI 上の optional 定義が undefined と解釈されるため、意図せず認識齟齬が発生する事象が起こっていました。

api.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Article(BaseModel):
    id: str
    content: str
    category: str | None


@app.get("/articles", response_model=list[Article])
def get_articles() -> list[Article]:
    return [{"id": "test1", "content": "test", "category": None}]

実際に生成される OpenAPI 定義

openapi: 3.0.2
info:
  title: FastAPI
  version: 0.1.0
paths:
  /articles:
    get:
      summary: Get Articles
      operationId: get_articles_articles_get
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema:
                title: Response Get Articles Articles Get
                type: array
                items:
                  $ref: "#/components/schemas/Article"
components:
  schemas:
    Article:
      title: Article
      required:
        - id
        - content
      type: object
      properties:
        id:
          title: Id
          type: string
        content:
          title: Content
          type: string
        category:
          title: Category
          type: string

終わりに

今回は、弊社のバックエンドシステムに採用している FastAPI のレスポンスで、None を undefined にして返却する方法をご紹介しました。

もちろん、バックエンドから返却する null を全て undefined にしてしまって問題ないか?は、各チームやプロダクトの特性によって異なります。もし、FastAPI を用いていてかつ、nullish な値を undefined で統一してしまっても問題ない場合はこの方法が有効です。

この記事や弊社に少しでもご興味持っていただけたら、是非カジュアル面談などにお越しください!
https://todoker.notion.site/efc2eea5eb054b6e8757fa3553af58d1

脚注
  1. https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter ↩︎

  2. https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict ↩︎

Discussion