📝

OpenAPIで確認するFastAPIの正しいスキーマ定義

2024/07/26に公開

スキーマのプロパティで、必須か任意かのプロパティを含める場合があると思いますが、
厳密に区別すると、必須か任意かの2パターンではなく、以下の4パターンあります。

  • プロパティが存在し、nullではない。
  • プロパティが存在し、nullでも良い。
  • プロパティが存在しなくても良いが、存在するならnullではない。
  • プロパティが存在しなくても良く、nullでも良い。

この4パターンの正しい定義ができているか確認するために、今回はOpenAPI Generatorを使って、フロントエンド(typescript)のコードを自動生成し、正しく定義できているか確認していきます。

OpenAPI Generatorとは

OpenAPI Generatorは、OpenAPI Specificationに則った仕様書からクライアントやサーバーのコードを生成できるツールです。

実はFastAPIは、OpenAPI Specificationに則った仕様書openapi.jsonを自動生成しています。このopenapi.jsonをOpenAPI Generatorに与えることで、クライアントやサーバーのコードを生成することができます。
つまり、FastAPIのスキーマ定義から自動でクライアントコードが生成することができるのです。

使用したバージョン

python == 3.11.4
fastapi == 0.101.1
pydantic == 2.2.1

正しいスキーマ定義

以下の3項目について記述していきます。

  • スキーマ定義
    pydanticのBaseModelを使ったスキーマの定義を記述します。ここで定義したスキーマをリクエストボディに設定します。
  • クライアントコード
    OpenAPI Generatorで生成したコード(typescript)のリクエストボディの型(interface)を記述します。
  • 仕様書(openapi.json)
    FastAPIが生成する仕様書(openapi.json)のスキーマ定義を記述します。

プロパティが存在し、nullではない。

スキーマ定義

class TestBody(BaseModel):
    number: int

クライアントコード

export interface TestBody {
  /**
   *
   * @type {number}
   * @memberof TestBody
   */
  number: number;
}
仕様書(openapi.json)
{
  "TestBody": {
    "properties": {
      "number": {
        "type": "integer",
        "title": "Number"
      }
    },
    "type": "object",
    "required": ["number"],
    "title": "TestBody"
  }
}

プロパティが存在し、nullでも良い。

スキーマ定義

class TestBody(BaseModel):
    number: int | None

クライアントコード

export interface TestBody {
  /**
   *
   * @type {number}
   * @memberof TestBody
   */
  number: number | null;
}
仕様書(openapi.json)
{
  "TestBody": {
    "properties": {
      "number": {
        "anyOf": [
          {
            "type": "integer"
          },
          {
            "type": "null"
          }
        ],
        "title": "Number"
      }
    },
    "type": "object",
    "required": ["number"],
    "title": "TestBody"
  }
}

プロパティが存在しなくても良いが、存在するならnullではない。

スキーマ定義

class TestBody(BaseModel):
    number: int = None

クライアントコード

export interface TestBody {
  /**
   *
   * @type {number}
   * @memberof TestBody
   */
  number?: number;
}
仕様書(openapi.json)
{
  "TestBody": {
    "properties": {
      "number": {
        "type": "integer",
        "title": "Number"
      }
    },
    "type": "object",
    "title": "TestBody"
  }
}

プロパティが存在しなくても良く、nullでも良い。

スキーマ定義

class TestBody(BaseModel):
    number: int | None = None

クライアントコード

export interface TestBody {
  /**
   *
   * @type {number}
   * @memberof TestBody
   */
  number?: number | null;
}
仕様書(openapi.json)
{
  "TestBody": {
    "properties": {
      "number": {
        "anyOf": [
          {
            "type": "integer"
          },
          {
            "type": "null"
          }
        ],
        "title": "Number"
      }
    },
    "type": "object",
    "title": "TestBody"
  }
}

おまけ:OpenAPI Generatorのコード生成方法

今回、OpenAPI Generatorを使って、クライアントコード(typescript)を生成した方法について説明します。ここでは、dockerを使ってOpenAPI Generatorを起動しています。

https://openapi-generator.tech/

openapi.jsonの取得

クライアントコードの生成には、openapi.jsonが必要です。FastAPIが自動生成しているopenapi.jsonを取得しましょう。FastAPIをhttp://localhost:8000/で起動していれば、http://localhost:8000/openapi.jsonから取得できます。

取得したopenapi.jsonをクライアントコードを生成したいディレクトリ(またはその上位のディレクトリ)に配置してください。

クライアントコードの生成

openapi.jsonのあるディレクトリで以下のコマンドを実行します。

docker run --rm \
    -v $PWD:/local openapitools/openapi-generator-cli generate \
    -i /local/openapi.json \
    -g typescript-axios \
    -o /local/{コードを配置するディレクトリ}

そうすると以下のようなファイルが指定したディレクトリに生成されます。

.
├── api.ts
├── base.ts
├── common.ts
├── configuration.ts
├── git_push.sh
└── index.ts

ここでは、typescript-axiosで生成しましたが、他に生成できるコードはたくさんあります。公式サイトで生成できるコードの一覧が確認できるので、自分のニーズに合う言語で生成してみてください。

https://openapi-generator.tech/docs/generators

Discussion