コンテンツにスキップ

APIドキュメント(後編)#


2024 iThome鉄人レース

これはDjango Ninjaシリーズチュートリアルの第18回です。

前回の記事では、Django NinjaがAPIドキュメントの表示に影響を与える重要な設定について探りました。それらは自動化APIドキュメントの基本であり、無視することはできません。

しかし、これだけでは不十分です!私たちはこのドキュメントをさらに生き生きとさせ、明確で分かりやすく読めるようにしたいのです。

その鍵となるのがAPIドキュメント上のデータサンプルです。良いサンプルは読むだけですぐに理解でき、理解や思考の時間を効果的に短縮できます。

この記事では、PydanticのField設定を活用して、APIドキュメントの明確さと可読性を全面的に向上させる方法を紹介します。自動生成されるドキュメントに生き生きとしたサンプルを追加し、ドキュメントをより現実に近づける方法を探ります。

この記事のすべてのコード変更は、このPRを参照してください。

GitHubサンプルプロジェクト#

👉 Django-Ninja-Tutorial


Django NinjaにおけるPydanticの役割#

Pydanticはデータ検証、シリアライズを実現するパッケージであり、FastAPIやDjango Ninjaなどのフレームワークで広く使用されています。

Django Ninjaにおいて、Pydanticはスキーマを定義するために使用され、これらのスキーマはAPIがHTTPリクエストとレスポンスのデータをどのように処理するかを決定し、自動的にOpenAPI標準に準拠したドキュメントに変換します。

Pydanticの強力な点は、データを検証できるだけでなく、Field設定を通じてドキュメントのフィールドに追加の説明、サンプル、デフォルト値を提供できることです。

これらの詳細設定は変換後のAPIドキュメントに自動的に反映され、開発者がAPIの動作や意味をより良く理解するのに役立ちます。

Pydantic Field#

PydanticのFieldは強力なツールであり、各データフィールドに対してタイトル、説明、サンプル、デフォルト値など、より詳細な情報を提供するために使用できます。

これらの設定はデータ検証に役立つだけでなく、APIドキュメントの可読性を大幅に向上させます。以下は一般的なFieldのパラメータです:

  • title:フィールドにタイトルを設定し、開発者がそのフィールドの役割を素早く理解できるようにします。
  • description:フィールドの説明を提供し、このフィールドの用途や制限をより明確に理解できるようにします。
  • examplesサンプル値を設定し、開発者がAPIの入力・出力フォーマットを直感的に理解できるようにします。
  • default第1位置引数であり、フィールドのデフォルト値を提供します。入力でこのフィールドの値が提供されていない場合、自動的にデフォルト値が使用されます。

これらのパラメータをうまく活用することで、高品質なAPIドキュメントを生成できます。

コードとドキュメントのバランス#

しかし!私たちは少し「現実」に寄り添う必要があります。すべてのAPIにこれほど多くの内容を書くことを要求すると、開発者に過度な負担を感じさせる可能性があります。

また、大量のパラメータを使用すれば確かにドキュメントは見栄えが良くなりますが、ドキュメントを生成するプログラムのコードは必然的に長々しくなってしまいます!

私たちは、十分な情報を提供しつつ、プログラムが冗長になりすぎないようなバランスポイントを見つける必要があります。

この観点から考えると、その中で最も重要な2つのパラメータは、defaultexamplesです——特に後者です!

そのため、この記事ではこの2つに焦点を当てて紹介します。これにより学習の焦点が絞れるだけでなく、私の日常的な開発にも合致しています。


公式ドキュメントとソースコード#

もしPydantic Fieldのパラメータや使い方についてさらに知りたい場合は、Django Ninjaではなく、Pydanticの公式ドキュメントを見る必要があります。

Django Ninjaのドキュメントには、Fieldの使用について専門に紹介する章はありません。これはFieldが実際にはPydanticの機能であり、Django Ninja特有のものではないためです。

しかし、実際にそのドキュメントを見ても、Fieldの全パラメータに対する解説が非常に詳細であるとは言えないことに気づくかもしれません。

利用可能なすべてのパラメータを知りたい場合は、ソースコードを見るのが最も早いと私は思います。そして、関数シグネチャ(そうです、Fieldは関数です)の型ヒント(型ヒント)からその使い方を推測するのも、良い方法の一つです。


以下では、Pydantic Fieldのexamplesdefaultパラメータを使用して、APIドキュメントをより生き生きと厳密にする方法について説明を始めます。

APIドキュメントに「サンプル」を追加する#

前回、現在のAPIドキュメントの不足点について言及しましたが、その中の「実際のサンプルが不足している」という問題はまだ解決されていません。

以下は「単一の記事情報を取得する」のドキュメントでのレスポンスサンプルです:

{
    "id": 0,
    "title": "string",
    "content": "string",
    "author": {
        "id": 0,
        "username": "string",
        "email": "string"
    },
    "created_at": "2024-09-22T08:58:55.960Z",
    "updated_at": "2024-09-22T08:58:55.960Z"
}

0であれ"string"であれ、良いドキュメントのサンプルとは言えません——どちらも現実味が足りないからです。

現在、私たちはレスポンスのスキーマにサンプルを追加しようとしています。コードは以下の通りです:

class _AuthorInfo(Schema):
    id: int = Field(examples=[1])
    username: str = Field(examples=['Alice'])
    email: str = Field(examples=['alice@exapmple.com'])

class PostResponse(Schema):
    id: int = Field(examples=[1])
    title: str = Field(examples=['Ninja is awesome!'])
    content: str = Field(examples=['This is my first post.'])
    author: _AuthorInfo
    created_at: datetime = Field(examples=['2021-01-01T00:00:00Z'])
    updated_at: datetime = Field(examples=['2021-01-01T00:00:00Z'])
    ...

ここには2つの重要なポイントがあります。

ポイント1:examplesパラメータ#

私はexamplesパラメータのみを使用しています。これが最もシンプルであり、さらにサンプルは確かにドキュメントにおいて非常に重要な部分だからです。

また、このexamplesには実は大きな意味合いがあります。もしexampleと書いた場合、例えば:

class PostResponse(Schema):
    id: int = Field(example=1)

実際には正常に動作しますが、Mypyは次のように警告を出します:

Unexpected keyword argument "example" for "Field"; did you mean "examples"?

その通りです。現在のPydantic v2では、FieldにはexamplesというパラメータしかありませんexampleはPydantic v1のやり方であり、Django Ninjaはまだ両方への互換性を維持しているのです。

将来を考慮すると、Mypyの警告を避け、最新バージョンのPydanticとの一貫性を保つためにも、やはりexamplesを使用することをお勧めします。

ポイント2:ネストされたスキーマのサンプル#

ネストされたスキーマのサンプルは、下層のスキーマにFieldを追加するだけで済みます。参照層で宣言する必要はありません:

class _AuthorInfo(Schema):  # これはネストの下層なので、Fieldを書く必要があります
    id: int = Field(examples=[1])
    username: str = Field(examples=['Alice'])
    email: str = Field(examples=['alice@exapmple.com'])

class PostResponse(Schema):
    ...
    author: _AuthorInfo  # 再度Fieldを書く必要はありません
    ...

実際の効果#

実際のAPIドキュメントのレスポンスを見てみましょう。ページ上のJSON値を直接キャプチャします:

{
    "id": 1,
    "title": "Ninja is awesome!",
    "content": "This is my first post.",
    "author": {
        "id": 1,
        "username": "Alice",
        "email": "alice@exapmple.com"
    },
    "created_at": "2021-01-01T00:00:00Z",
    "updated_at": "2021-01-01T00:00:00Z"
}

以前の0"string"と比べて、より生き生きとして読みやすくなったのではないでしょうか?


defaultパラメータの正しい使用タイミング#

私の見解では、ほとんどの場合、私たちはデフォルト値を定義する必要はありません

この書き方もお勧めしません

class PostResponse(Schema):
    id: int = 1
    ...

ドキュメント上では同様にサンプル値として1が表示されますが、実はこの書き方は以下の書き方と等価です:

class PostResponse(Schema):
    id: int = Field(default=1)
    ...

これは実際にはデフォルト値を定義しています。 先に述べたように、もしスキーマがHTTPリクエストに使用され、かつクライアントがそのフィールドの値を提供していない場合、Django Ninjaは自動的にデフォルト値を使用します。

これは予想外の結果を引き起こす可能性が高いです。

正しいフローは:フロントエンドが値を提供していない時、Django Ninjaは422レスポンスを出すべきです。

したがって、基本的にデフォルト値を定義する必要はありません(定義すべきでもありません)。ただし、次の場合は例外です

オプションフィールドでデフォルト値Noneを使用する#

私が個人的に推奨するのは、リクエストフィールドが「オプション(optional)」である場合にのみ、defaultパラメータを使用することです。

そしてこの時のデフォルト値はNoneであるべきです。

デモのために、新しいAPI——「ユーザー追加」(つまりユーザー登録)を作成します。このAPIは、今後のチュートリアルでも繰り返し言及され、改善されていきます。

私たちのUserモデルにおいて、bioフィールドがオプションであったことを覚えていますか?

class User(AbstractUser):
    email = models.EmailField(unique=True)  # 強制的に一意のemail
    bio = models.TextField(null=True)  # 自己紹介フィールド(オプション)
    ...

そのため、私たちのAPIリクエストのスキーマは以下のようになります——直接bioフィールドの設定を見てみましょう:

class CreateUserRequest(Schema):
    ...
    bio: str | None = Field(
        default=None,
        examples=['Hello, I am Alice.']
    )

Field内のdefault=Noneの設定により、クライアントが値を入力しなかった場合でも、APIはエラーになりません。

また、bio: str | Noneという型ヒント(型ヒント)に注意してください。絶対にNoneを省略してはいけません。ドキュメントのレンダリング結果に影響を与えます:(これはNoneがある場合の結果です)

Noneがあって初めて、APIドキュメントはそのフィールド値がオプション(string | null)であることを表示します。


まとめと次のステップ#

本章の学習と改善を経て、私たちのAPIドキュメントはすでに80点の水準に達しました!ほとんどの開発プロジェクトにおいて、このようなドキュメントの品質はかなり優れていると言えます。

次は第5章——データ検証エラー処理に入ります。

この章では、Django Ninjaで効果的なデータ検証を実装する方法や、起こりうる様々なエラー状況にエレガントに対処し、レスポンスを返す方法について網羅します。

これらのテクニックを通じて、私たちはより堅牢で信頼性の高いAPIを構築できるようになります。