盆暗の学習記録

データサイエンス 、ソフトウェア開発、ビジネスについて日々学んだことの備忘録としていく予定です。初心者であり独学なので内容には誤りが含まれる可能性が大いにあります。

FastAPIを使ってOpenAPI仕様書と、TypeScriptクライアントを自動生成する

FastAPIを使うと色々便利そうだなとあらためて認識して触ってたのでメモしておきます。

FastAPI → OpenAPI仕様書

FastAPIはPythonでのWebAPI開発が簡単にできるフレームワークです。

Pythonの型ヒントをもとにデータ型のバリデーションもしてくれて、簡潔なコードでAPI開発ができます。

from fastapi import FastAPI
from typing import Literal

app = FastAPI()

Animal = Literal["cat", "dog", "bird"]

@app.get("/favorite-animal")
async def favorite(name: Animal) -> str:
    return f"{name}s are cute!"

FastAPIの便利機能の一つが、ソースコードからOpenAPIによる仕様書を自動生成してくれる点です。

fastapi.tiangolo.com

これにより仕様書を自分で書く必要がなくなります。

OpenAPI → TypeScriptのクライアント

フロントエンド側でAPIを呼び出すコードをTypeScriptで書く場合もOpenAPIがあると役立ちます。

OpenAPI TypeScript

OpenAPI TypeScript を使うと、APIのメソッドの入出力の型定義ファイルを生成できます。

インストール

npm i -D openapi-typescript typescript

tsconfig.json も追加します。

{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "noUncheckedIndexedAccess": true
  }
}

型定義ファイルの生成

次のコマンドで型定義ファイルを生成できます。生成されるのは.d.tsファイル1つだけです。

npx openapi-typescript ./path/to/my/openapi.json -o ./path/to/my/schema.d.ts

openapi-fetch

OpenAPI TypeScriptはfetchやSWRやTanStack Quaryのラッパーの生成機能もあります。

npm i openapi-fetch
import createClient from "openapi-fetch";
import type { paths } from "./my-schema"; // 生成したd.tsファイル
const client = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });
const { data,  error } = await client.GET("/blogposts/{post_id}", {
  params: { path: { post_id: "123" }  },
});

非常に便利です。

openapi-ts.dev

@hey-api/openapi-ts

@hey-api/openapi-ts はOpenAPI TypeScriptよりも明示的にクライアントコードを生成するパッケージです。

https://github.com/hey-api/openapi-ts

まだ発展途上のようですが、頻繁にアップデートされています。

次のコマンドでデモを実行することができます。

npx @hey-api/openapi-ts \
  -i https://get.heyapi.dev/hey-api/backend \
  -o src/client

こんな感じでたくさんコードが生成されます。

src
└── client
    ├── client
    │   ├── client.ts
    │   ├── index.ts
    │   ├── types.ts
    │   └── utils.ts
    ├── client.gen.ts
    ├── core
    │   ├── auth.ts
    │   ├── bodySerializer.ts
    │   ├── params.ts
    │   ├── pathSerializer.ts
    │   └── types.ts
    ├── index.ts
    ├── sdk.gen.ts
    └── types.gen.ts

ただ、少し使ってみたところ、シンプルなAPIに対してでも多量のコードが生成され、認証がないAPIについても認証についてのコードが生成されるなど、個人的にはよくわからない挙動があったりで使いにくく感じました (理解が深まれば問題ないのかもしれませんが、学習コストが高そう)

OpenAPI → テスト(余談)

ちなみに、OpenAPIから property-based testing(PBT;仕様書通りの入出力ができるかのテスト)ができる Schemathesis というパッケージも存在します。

schemathesis.readthedocs.io

導入がとても簡単で、コマンドから実行したり

schemathesis run https://your-api.com/openapi.json

pytestで実行するスクリプトを作ったり

import schemathesis
schema = schemathesis.openapi.from_url("https://your-api.com/openapi.json")

@schema.parametrize()
def test_api(case):
    case.call_and_validate()  # Finds bugs automatically

Github Actionsに3行足すだけで実行できたりします

- uses: schemathesis/action@v2
  with:
    schema: "https://your-api.com/openapi.json"

ただ、FastAPIから作成したOpenAPI仕様書だと両者の間に差異は無いはずなので、多くの場合でPBTはパスします。そのため有効性はあんまり高くない気がします…。

私が簡単に試した感じでは、Pythonにおいてはboolがintのsubclassであるのに対してSchemathesisのテストではboolとintを明確に区別することを期待していた関係でそこだけFailしてました。