📝

FastAPIのOpenAPIの出力メモ

に公開

概要

FastAPIでSwagger UIを生成するのは簡単ですが、設定できるオプションによって表示がどのように変わるのかを解説した記事がぱっとで見当たりませんでした。
そこで、自分で調べた結果を整理してご紹介します。

今回もソースコードはGitHubにあげていますので参考にしてください
https://github.com/merutin/fastapi-api

前提知識

OpenAPI

バックエンドとフロントエンドの境界でルールを決めるためのツールの一つです。
ぱっと試すには Swagger Editor が便利です

https://swagger.io/specification/

詳細。ChatGPTに聞いた内容です。

OpenAPI(以前はSwaggerと呼ばれていた)は、RESTful API の仕様を機械可読なフォーマット(JSON/YAML)で定義するためのオープンな標準です。以下のような特徴があります。

  • 自己記述性
    API のエンドポイント、リクエスト/レスポンスの構造、認証方法、利用可能なパラメータなどをひとつのファイルでまとめられるため、ドキュメントと実装の乖離を防ぎやすい。
  • ツール連携
    OpenAPI 仕様を基に、Swagger UI や ReDoc などのドキュメント生成ツール、さらにクライアント/サーバーのスタブコード自動生成ツールと組み合わせて利用できる。
  • 言語・フレームワーク非依存
    JSON/YAML さえ出力できればどんな言語・フレームワークでも対応可能で、エコシステムが非常に豊富。

FastAPI はこの OpenAPI 仕様を自動生成し、さらにカスタマイズオプションも豊富に用意しているため、手軽に高品質な API ドキュメントを揃えたい開発者に最適です。

FastAPI

FastAPI は、Pythonの標準である型ヒントに基づいてPython 以降でAPI を構築するための、モダンで、高速(高パフォーマンス)な、Web フレームワークです。
https://fastapi.tiangolo.com/ja/

型ヒントを使うという点が特徴的だなと個人的には感じます。

詳細。ChatGPTに聞いた内容です。

FastAPI は、Python 製のモダンな Web フレームワークで、以下のような特徴があります。

  • 高速・高性能
    ASGI(Asynchronous Server Gateway Interface)に準拠し、非同期(async/await)を活かした処理で高いスループットを実現。Starlette をベースにしており、Node.js や Go と同等クラスの性能を発揮します。
  • 型ヒントによる自動バリデーション
    Python の型ヒントと Pydantic を組み合わせて、リクエストボディやクエリパラメータの型チェックを自動化。開発者は煩雑なバリデーション処理を書かずに済み、安全性と可読性が向上します。
  • 自動 API ドキュメント生成
    OpenAPI 仕様の openapi.json と、Swagger UI/ReDoc のインタラクティブなドキュメントをデフォルトで提供。エンドポイントを定義するだけで、すぐに使える API ドキュメントが手に入ります。
  • シンプルかつ直感的なコード
    デコレーターと関数定義だけでルーティングや入出力モデルが定義でき、学習コストが低く、チーム開発にも適しています。
  • 拡張性・互換性
    ミドルウェアやプラグイン、依存性注入(Dependency Injection)もサポート。既存の ASGI ミドルウェアをそのまま利用でき、認証やキャッシュなど多彩な拡張が可能です。

これらの機能により、FastAPI は「簡単に書けて速く動く」API サーバーを手軽に構築できるフレームワークとして、幅広いプロジェクトで利用されています。

FastAPIのOpenAPI出力について

ローカルで起動した後に、 /docs にアクセスするとSwaggerUIを表示します。
/openapi.json にアクセスするとOpenAPIのJsonが表示されます。

最初から試してみたい場合、公式のチュートリアルを参考にいじってみてください
https://fastapi.tiangolo.com/ja/#_2

Titleなど

タイトルやアプリケーションの説明、バージョンなどはFastAPIの引数として設定します。

以下は画像のもとになったソースコードです。
他にもいろいろなオプションがあるのですが、私は使ったことないものが多いため割愛します。

app = FastAPI(
    title="Sample API",
    summary="Sample API for demonstration",
    description="This is a sample API for demonstration purposes.",
    version="1.0.0",
    contact={
        "name": "API Support",
        "url": "https://example.com/support",
    },
    openapi_tags=[
        {
            "name": "users",
            "description": "Operations with users. The **login** logic is also here.",
        },
        {
            "name": "items",
            "description": "Manage items. So _fancy_ they have their own docs.",
            "externalDocs": {
                "description": "Items external docs",
                "url": "https://fastapi.tiangolo.com/",
            },
        },
    ],
    license_info={
        "name": "Apache 2.0",
        "url": "https://www.apache.org/licenses/LICENSE-2.0.html",
    },
    servers=[
        {"url": "https://stag.example.com", "description": "Staging environment"},
        {"url": "https://prod.example.com", "description": "Production environment"},
    ],
)

title

titleの設定をします。指定しないと FastAPI と表示します。

summary、description

タイトルの下に出る説明を設定します。
画面の表示上にはあまり違いがありませんが、summary、descriptionの順に並んでいます。

descriptionはソースコードを見るとmarkdownが利用できるっぽく見えるのですが、Swagger UIでは以下のように出力されます。ほかのツールだともっときれいに出力されるのかもしれません

https://github.com/fastapi/fastapi/blob/0.115.12/fastapi/applications.py#L161-L176

version

タイトルの横にある灰色の数字を設定します。
設定しない場合、0.1.0となります。
OAS 3.1はOpenAPIのバージョンです。

contact

URLを設定できます。

license_info

ライセンス情報を設定できます。

openapi_tags

APIにつけるタグを定義します。
ここで定義しなくてもAPIのみの定義でも正しく動作しますが、descriptionを書きたい場合は先に定義する必要があります。
上記の定義をすると以下のように出力します。

servers

開発環境やstg環境、prodなどのURLを設定できます。
値を設定するとAPIの Try it outのボタンから実行するAPIのURLになります。

API

APIごとに設定できる内容です

Parameter

メソッドの引数として設定するとパラメータになります

@app.get("/")
def root(id: str) -> RootResponse:

複数あってわかりにくい場合は以下のような定義もできます

class RootParams(BaseModel):
    id: str = Field(description="The ID of the message to retrieve")

@app.get("/message/")
def root(param: Annotated[RootParams, Query()]) -> RootResponse:

Body

Bodyに設定した値はclassとして定義します

class MsgBody(BaseModel):
    msg_name: str = Field(..., description="The name of the message")

    model_config = {
        "json_schema_extra": {
            "examples": [
                {"msg_name": "Hello"},
            ]
        }
    }

# Route to add a message
@app.post("/messages/")
def add_msg_body(request: MsgBody) -> dict[str, MsgPayload]:
    return {"message": request.msg_name}

classのフィールドにdescriptionを書くと、bodyのSchemaに表示されます。
わかりにくい。。

Path Parameter

URIのパスとして利用します。変数を設定したい場合は {path} のように指定をします。
変数はメソッドの引数に設定されます

@app.get("/{path}")
def root(path: str) -> RootResponse:

Response

別途指定もできますが、Responseの値は関数のreturnの値になります。
下記の場合、RootResponseになります。

class RootResponse(BaseModel):
    message: str

    model_config = {
        "json_schema_extra": {
            "examples": [{"message": "Hello"}, {"message": "Welcome to the API"}]
        }
    }

@app.get("/")
def root(id: int) -> RootResponse:
    return RootResponse(message="Hello")

422のResponse

Parameter、Path Parameter、Bodyなどを定義すると、Responseに422が自動的に入ります。
これはpydanticの定義と実際の値がことなるときにFastAPI側で自動的に作成するエラーのようです

その他

Formやファイルの場合は別の記述が必要になりますが、OpenAPI的にはそれほど違いがないので割愛します。
気になる方は参考リンクを見てみてください。
https://fastapi.tiangolo.com/ja/tutorial/request-forms/
https://fastapi.tiangolo.com/ja/tutorial/request-files/

デコレータ

デコレータにより詳細な内容は記載ができます。
なお、最初の引数はpathとして判断してくれますが、それ以降は名前の指定が必要です。
@app.get("/")@app.get(path="/") はOKだが、 @app.get("/", "hoge")は上手くいかないみたいな感じです。

@app.get(
    path="/{path}",
    summary="Root endpoint",
    description="This is the root endpoint of the API.",
    tags=["Root"],
    status_code=200,
    response_description="A simple greeting message",
    responses={
        202: {
            "description": "Accepted",
            "content": {
                "application/json": {"example": {"message": "Hello"}},
                "text/plain": {"success"},
            },
        },
        400: {
            "model": ErrorResponse,
        },
        404: {
            "description": "Not Found",
            "content": {"application/json": {"example": {"detail": "Item not found"}}},
        },
    },
)
def root(id: str) -> RootResponse:

status_code

デフォルトのステータスコードを設定します。指定しない場合は200になります。
responsesを設定しない場合のstatus_codeとして設定されるのと、実際のレスポンスのステータスコードも設定した値になります。

response_description

デフォルトのステータスコードの場合の説明を記載します。
responsesで別途定義している場合はそっちが優先されます

summary、description

summaryはURIの横に表示します。
descriptionはURIの下の行に入ります

デコレータで記載していない場合でも、関数のコメントを記載していると、descriptionに入ります

@app.get("/")
def root(id: int) -> RootResponse:
    """
    ここもdescriptionとして扱う
    """
    return RootResponse(message="Hello")

tags

tagごとにグルーピングを行います。
設定しない場合はdefaultになります。

FastAPIの引数で定義していない場合でも、新しくTagを定義できます。その場合、おそらく定義順にTagが並ぶため順番を制御したい場合はFastAPI側の引数に入れておいた方が無難です。

responses

デフォルト以外のレスポンスを設定します。
キーとして400などのステータスコードを設定します。
modelを指定した場合は、Modelの値がSwagger UI上に反映されます。
descriptionやContentを指定した場合はその内容が反映されます。

上記の例では、正常系として200、追加で定義しているのが、202、400、404、バリデーションエラーの422が追加になって、全部で5つのResponsesが生成されます。

まとめ

省いている部分もありますが、主要なオプションについてはキャプチャ付きでまとめられたと思います。
何を設定したらいいかわからない場合に参照していただければと思います。

参考文献

https://fastapi.tiangolo.com/ja/learn/

DELTAテックブログ

Discussion