ChatGPT先生と挑むFastAPI #2|GET APIとパラメータ

記事内に広告が含まれています。
タイトル

STEP 2では、GET APIで値を受け取るためのパスパラメータとクエリパラメータを学びます。
URLと関数の引数がどのように結び付くのかを確認し、実際にAPIを作成します。

また、型指定によるデータ変換やバリデーション、必須・任意パラメータの違いも確認します。

ChatGPT先生
ChatGPT先生

それではSTEP2を始めましょう!

■■■スポンサーリンク■■■

今回学ぶこと

今回のステップで学ぶ内容を一覧で出してもらいました。

ChatGPT先生
ChatGPT先生

STEP2では以下の内容を学習します。

  1. GET APIで扱うパラメータの全体像を理解する
  2. パスパラメータを使うGET APIを実装・動作確認する
  3. 型指定によるデータ変換とバリデーションを確認する
  4. クエリパラメータを使うGET APIを実装・動作確認する
  5. クエリパラメータの必須・任意・デフォルト値を理解する
  6. パスパラメータとクエリパラメータを組み合わせる
  7. Swagger UIでパラメータのAPI仕様を確認する
  8. 自力課題

学習開始

それでは学習をはじめます。

GET APIで扱うパラメータの全体像を理解する

ChatGPT先生
ChatGPT先生

まずはパスパラメータとクエリパラメータについて理解します。

STEP 1では、固定URLのGET APIを作りました。

@app.get("/")
def read_root():
    return {"message": "Hello World"}

この場合、アクセスするURLは常に / で、クライアントから特別な値を受け取っていません。

実際のAPIでは、/users/10のように「IDが10のユーザーを取得したい」こともあれば、/users?limit=20のように「最大20件取得したい」と指定することもあります。

ここで登場するのが、今回の主役である パスパラメータ と クエリパラメータ です。

種類URL例主な用途
パスパラメータ/users/10特定のリソースを指定する
クエリパラメータ/users?limit=20検索・絞り込み・並び替えなどの条件を指定する
ChatGPT先生
ChatGPT先生

FastAPIで特に押さえておきたいところがFastAPIではどう区別するのかです。

たとえば、次のようなGET APIのコードがあります。

@app.get("/users/{user_id}")
def get_user(user_id: int, limit: int = 10):
    return {
        "user_id": user_id,
        "limit": limit,
    }

@app.get() に指定されているパスに{user_id}のように {} で書くと、その部分がパスパラメータになります。

get_user関数の引数を見ると、@app.get()のパスに存在しないパラメータ:limitがあります。これがクエリパラメータになります。

実際のリクエストがGET /users/123?limit=5の場合、FastAPIは次のように判断します。

123:パスに書かれているためuser_id → パスパラメータ
limit=5:パスには書かれていないがPython関数の引数に定義されている → クエリパラメータ

ChatGPT先生
ChatGPT先生

クエリパラメータ名と関数の引数名は、基本的に同じ名前で対応付けられます。

DRFでは、パスパラメータやクエリパラメータは以下のように取得していた値を、FastAPIでは関数の引数として宣言すること自体がAPI仕様になるといった感じです。

self.kwargs.get("user_id")
request.query_params.get("limit")

パスパラメータを使うGET APIを実装・動作確認する

ChatGPT先生
ChatGPT先生

今回はまず、パスパラメータだけに集中します。クエリパラメータはまだ実装しません。

STEP 1で作成したFastAPIアプリに、次のGET APIを追加してください。

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {
        "user_id": user_id,
    }

引数の定義でuser_id: intとintを指定しているので、FastAPIはURLから受け取った文字列を整数として扱おうとします。

ChatGPT先生
ChatGPT先生

APIを起動した状態で、ブラウザまたはSwagger UIから、GET /users/123へアクセスしてみてください。

期待するレスポンスは、{"user_id": 123}です。

実行結果↓

パスパラメータの確認結果

型指定によるデータ変換とバリデーションを確認する

ChatGPT先生
ChatGPT先生

では次にGET /users/abcへアクセスしてみてください。
実装したコードではuser_id: intと整数型を指定しています。

整数に変換できない場合にどうなるのか見てみましょう。

実行結果↓

バリデーションエラー

"msg"に「有効な整数として解釈できません」というエラー内容が書かれています。

ChatGPT先生
ChatGPT先生

処理の流れとしてはこうです。

GET /users/abc
↓
パスの {user_id} に “abc” が入る
↓
user_id: int なので整数への変換を試みる
↓
“abc” は int に変換できない
↓
バリデーションエラー
↓
get_user() は実行されない

get_user() の中でチェックしているわけではありません。関数が呼ばれる前にFastAPI側が型を確認し、問題なければ変換済みの値を関数へ渡してくれるわけです。

クエリパラメータを使うGET APIを実装・動作確認する

ChatGPT先生
ChatGPT先生

今度は、クエリパラメータだけを使うAPIを追加します。

STEP 1のコードに、次を追加してください。

追加するコード↓

@app.get("/users")
def get_users(limit: int = 10):
    return {
        "limit": limit,
    }
ChatGPT先生
ChatGPT先生

今回注目するのはここです。

パスには/usersしか書かれていません。一方、関数にはlimit: int = 10という引数があります。
つまり、limitは「パスには存在しないが関数の引数として定義されている」ため、FastAPIではクエリパラメータとして解釈されます。

GET /users?limit=5にアクセスしてください。期待するレスポンスは{"limit": 5}です。

実行結果↓

クエリパラメータの確認

クエリパラメータの必須・任意・デフォルト値を理解する

省略可能なクエリパラメータ

ChatGPT先生
ChatGPT先生

先ほど実装したAPIの引数はlimit: int = 10となっています。

この= 10があることでlimit は省略可能なクエリパラメータになります。

つまり、GET /usersのようにlimit 自体を送らなかった場合は、limit = 10として関数が実行されます。

コードは変更せず、GET /usersにアクセスしてください。期待するレスポンスは{"limit": 10}です。

実行結果↓

ChatGPT先生
ChatGPT先生

コードを一時的に変更します。limit: int = 10の= 10を削除してください。

変更後のコード↓

@app.get("/users")
def get_users(limit: int):
    return {
        "limit": limit,
    }
ChatGPT先生
ChatGPT先生

limitのデフォルト値をなくしたことにより、FastAPIは limit を必須のクエリパラメータとして扱います。

もう一度GET /usersへアクセスしてみてください。バリデーションエラーになるはずです。

実行結果↓

ChatGPT先生
ChatGPT先生

"msg"に「必須項目です」というエラー内容が書かれています。

つまり今のdef get_users(limit: int):は、limit はクエリパラメータとして必須という定義になっています。

ここまでで、まず以下の違いは確認できました。

  • デフォルト値あり → 省略可能
  • デフォルト値なし → 必須

任意のクエリパラメータ

ChatGPT先生
ChatGPT先生

次に「None を許可する任意のクエリパラメータ」 を確認します。

コードの引数の部分をlimit: int | None = Noneに変更してください。

変更後のコード↓

@app.get("/users")
def get_users(limit: int | None = None):
    return {
        "limit": limit,
    }
ChatGPT先生
ChatGPT先生

int | Noneは値は int または None を許可するという意味です。
= Noneは指定されなかった場合のデフォルト値は Noneという意味です。

GET /usersにアクセスしてください。期待値は{"limit": null}です。

実行結果↓

任意のクエリパラメータの確認結果
ChatGPT先生
ChatGPT先生

ここまでの3パターンを整理すると、つぎのようになります。

特に大事なのは、int | None だけで「省略可能」になるわけではなく、= None があるから省略可能になるという点です。

定義limit を省略できるか省略時
limit: int❌バリデーションエラー
limit: int = 10✅10
limit: int | None = None✅None
クエリパラメータの省略パターンのまとめ

パスパラメータとクエリパラメータを組み合わせる

ChatGPT先生
ChatGPT先生

ここまで別々に扱ってきた2種類のパラメータを、今度は1つのAPIで組み合わせます。

次のGET APIを追加してください。

追加するコード↓

@app.get("/users/{user_id}/items")
def get_user_items(
    user_id: int,
    limit: int = 10,
):
    return {
        "user_id": user_id,
        "limit": limit,
    }
ChatGPT先生
ChatGPT先生

今回のコードでは、それぞれの役割がこうなります。

@app.get("/users/{user_id}/items")
                    # ↑ パスに存在する
                    #   → user_id はパスパラメータ

def get_user_items(
    user_id: int,     # ← パスパラメータ
    limit: int = 10,  # ← パスには存在しない
                      #   → クエリパラメータ
):

/users/123/items?limit=5へアクセスしてみてください。期待するレスポンスは{"user_id": 123, "limit": 5}です。

実行結果↓

実行結果

Swagger UIでパラメータのAPI仕様を確認する

ChatGPT先生
ChatGPT先生

ここでは新しいコードは書かず、FastAPIがこれまでの定義からどのようなAPI仕様を自動生成しているかを確認します。

http://localhost:8000/docsへアクセスしてください。Swagger UIが表示されたら、今回追加した、GET /users/{user_id}/itemsを開いてください。

Swagger UI
ChatGPT先生
ChatGPT先生

「Parameters」にuser_idとlimitが表示されています。ここで注目してほしいのは、FastAPIがコードのパスと引数から自動的に読み取って以下のようなAPI仕様を生成している点です。

user_id
→ path parameter
→ integer
→ 必須

limit
→ query parameter
→ integer
→ デフォルト値 10

つまり、開発者はSwagger用に別途、
「user_idはパスパラメータです」
「limitはクエリパラメータです」
とは書いていません。

Pythonの関数定義そのものがAPI仕様として利用されているわけです。

自力課題

課題内容

ChatGPT先生
ChatGPT先生

次の仕様のAPIを作ってください。

エンドポイント:GET /books/{book_id}/reviews

パラメータ

  • book_id
    • パスパラメータ
    • int
    • 必須
  • limit
    • クエリパラメータ
    • int
    • 省略可能
    • デフォルト値は 10
  • offset
    • クエリパラメータ
    • int または None
    • 省略可能
    • 省略時は None

レスポンスの例:

GET /books/25/reviews?limit=5&offset=10の場合は次のようなJSONを返してください。

{
  "book_id": 25,
  "limit": 5,
  "offset": 10
}

また、GET /books/25/reviewsなら次のようなJSONを返してください。

{
  "book_id": 25,
  "limit": 10,
  "offset": null
}

完了条件

ChatGPT先生
ChatGPT先生

次の3パターンを確認してください。

/books/25/reviews?limit=5&offset=10
/books/25/reviews
/books/abc/reviews

3つ目では、book_id が int に変換できないため、FastAPIのバリデーションエラーになることも確認してください。

実装結果

実装したコード↓

@app.get("/books/{book_id}/reviews")
def get_book_reviews(
    book_id: int, 
    limit: int = 10, 
    offset: int | None = None
):
    return {
        "book_id": book_id,
        "limit": limit,
        "offset": offset,
    }

エンドポイント:/books/25/reviews?limit=5&offset=10の実行結果

実行結果1

エンドポイント:/books/25/reviewsの実行結果

実行結果2

エンドポイント:/books/abc/reviewsの実行結果

ChatGPT先生のレビュー結果

ChatGPT先生
ChatGPT先生

ばっちりです。自力課題は要件どおりに実装できています。
確認結果も3パターンとも正しいです。

まとめ

今回はFastAPIにおけるパスパラメータとクエリパラメータの扱いについて学びました。

パスパラメータ

  • 特定のリソースを指定するために用いられる
  • Path Operation Decoratorのパス内に{}で囲って記載する

クエリパラメータ

  • 検索・絞り込み・並び替えなどの条件を指定する
  • Path Operation Decoratorに記載はないが、関数の引数として定義されている場合、FastAPIはパスパラメータと判断
  • 引数の定義にデフォルト値を指定することで省略可能にすることができる

どちらも、関数の引数定義に型を指定することで、FastAPIがAPIの仕様としてその型を採用します。
もし指定した型に変換できない値がリクエストされた場合は、関数を実行せずにバリデーションエラーを返します。

そのため、関数側でパラメータの型チェックを実装する必要がないのは手間が省けていいなと思いました。
(当たり前だが、バリデーションチェックが完全に不要になるわけではない。業務上必要なチェックは実装する必要がある。)

Path Operation Decoratorと関数の引数からAPIドキュメントにも自動でパラメータが反映されるのも便利だと感じました。

コメント

タイトルとURLをコピーしました