4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FastAPI Tutorialメモ その2 Path Parameters

Posted at

はじめに

FastAPI Souce: https://github.com/tiangolo/fastapi
FastAPI Document: https://fastapi.tiangolo.com

対象: Path Parameters ( https://fastapi.tiangolo.com/tutorial/path-params/ )

FastAPIチュートリアルのメモ。

FastAPI Tutorialメモ その1の続きになります。
FastAPIインストールやサーバの起動は上記のものと同様となりますので、上記記事をご確認ください。

基本的にはFastAPIの公式チュートリアルを参考にしていますが、自身の学習のため一部分を省略したり順番を前後させています。
正しい詳細な情報は公式ドキュメントを参考いただければと思います。

Web & Python 初心者かつ翻訳はGoogleとDeepL頼りのため、間違い等ありましたらご指摘いただけますと幸いです。

開発環境

Ubuntu 20.04 LTS
Python 3.8.2
pipenv 2018.11.26

目標

  • FastAPIのPathパラメータの理解

手順

Intro

FastAPIではパスに対するパラメータや変数をPythonの書式と同じ構文で宣言することができます。
以下のコードをmain.pyとして保存してください。

main.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id):
    return {"item_id": item_id}

この時、デコレータ関数@app.get("/items/{item_id}")item_idPathパラメータであり、引数item_idとして関数の引数に渡されます。
例としてこのファイルを保存した後にサーバを起動(uvicorn main:app --reload)し、 http://127.0.0.1:8000/items/foo をブラウザで開いてみてください。下記の通り、変数item_idに文字列fooが代入されたJSON形式のテキストが返ってくるはずです。

{"item_id":"foo"}

実際の画面

qiita_2_1 2020-07-12 22-53-23.png

これがFastAPIにおけるPathパラメータの基本となります。

型付きのPathパラメータ

FastAPIではPython標準の型アノテーションを使用して、関数内でPathパラメータの型を宣言することができます。

main
~
# 前略
~

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

関数を宣言しているasync def read_item(item_id: int):の行に注目してください。ここでは、引数item_idint(整数型)として宣言しています。
型宣言によって、エラーチェックや単語の補完などのエディタサポートを受けられるようになるでしょう。(上記のコードでは、foo = item_id + "bar"のように数字と文字列を足す処理をすれば私の環境(PyCharm)ではType Checkerによるエラーが表示されました。)

main.pyの関数部分を上記のコードに変更した状態で http://127.0.0.1:8000/items/3 をブラウザで開くと、次のように表示されます。

{"item_id":3}

ここで、関数read_itemが受け取り、返り値として表示した値が文字列ではなく整数の3であることに注意する必要があります。
URLへの入力はデフォルトではすべて文字列として解釈されますが、Pythonの型宣言によってFastAPIは自動的にリクエストをparsing(解析)し、整数型へ変換したのです。

ためしに型を宣言している部分を取り除いたコード(async def read_item(item_id: int): の部分をasync def read_item(item_id):に戻す)で http://127.0.0.1:8000/items/3 にアクセスると文字列"3"として表示されるでしょう。

{"item_id":"3"}

また、int型として宣言したPathパラメータに対して、 http://127.0.0.1:8000/items/foo のようなURLでアクセスした場合は下記のようなHttpエラーが返されます

{
    "detail": [
        {
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

与えられたパラメータ(str型"foo")がint型に変換することができないため、FastAPIのData validation(データの検証)機能が働いたためです。
上記のstr型同様に、浮動少数点数float型を与えた場合( http://127.0.0.1:8000/items/4.2 など)も同様のエラーになります。

なお、float型で宣言したパラメータにint型の値を与えた場合は変換されることが確認できました

上記から、FastAPIはPythonの型宣言と全く同じバリデーションを行っていることがわかります。

エラーメッセージにはバリデーションが通過しなかったポイント(今回なら"path"パラメータの変数"item_id")も明記されていることに注目できます。APIと相互作用するコードの開発やデバッグに非常に役立つでしょう。

型チェックによるバグの防止は、FastAPIの重要な特徴の一つです。

ドキュメントの確認

上記のコードを確認したら、ブラウザで http://127.0.0.1:8000/docs を確認してください。以下のように、インタラクティブ(対話的)なAPIのドキュメントが自動的に生成されています。(画面中央付近のdefaultの下のGETと表記された青いタブをクリックすれば展開されます。)

qiita_2_2 2020-07-13 19-25-40.png

ParametersのNameitem_idの下にintegerとありますね。Pythonの型宣言が(Swagger UIに統合された)インタラクティブなドキュメントにも反映されていることがわかるかと思います。
このドキュメントではAPIの様々な情報を得ることができ、また、デザイナーやフロントサイドの人々と共有することが可能です。

また、このドキュメントはFastAPIが生成したスキーマを元にしたものですが、生成されたスキーマはOpenAPIに準拠しており、様々な互換性のあるツールが開発されています。
メモその1でも確認したましたが、FastAPIではReDocを使用した代替ドキュメントも存在しています。
http://127.0.0.1:8000/redoc にアクセスしてみてください。

qiita_2_3 2020-07-13 19-40-37.png

ざっくりとした個人的な印象ですが、Swagger UI は実際にリクエストを生成して結果の確認までできるインタラクティブ(対話的)なドキュメント、一方のReDocは静的でシンプルなドキュメントという認識です。おそらくもっと色々な使い方や別のツールがあるのでしょうが、勉強不足でまったくわかりません。すみません。学習次第、更新していきたいと思います。

今のところ、リクエストを生成して結果も見れるSwagger UIが便利だなぁという気持ちです。

Pydantic

本題に戻りまして、このようなドキュメント生成にも役立つ型アノテーションは、すべてPydanticというライブラリによって行われています。
Pydanticについては下記の記事が参考になりました。
Pydantic 入門 @0622okakyo

Pydanticにより、strfloatboolなど多くのデータ型で、すべて同様に型アノテーションを行うことができます。

Path Operationにおける順序問題

Path Operationを作成する場合、パスが固定されてしまう状況になることがあります。

例として、ユーザが自身のデータを取得するための/users/meというパスがあるとします。
この時、同時にある別のユーザに関するデータをuser_idを使用して取得する/users/{user_id}というパスも存在するとしましょう。

FastAPIにおけるPath Operationは順番に評価されていきます。
よって、下記のようにパス/users/meはパス/users/{user_id}よりも前に宣言される必要があります。

main
from fastapi import FastAPI

app = FastAPI()


@app.get("/users/me")
async def read_user_me():
    return {"user_id": "the current user"}


@app.get("/users/{user_id}")
async def read_user(user_id: str):
    return {"user_id": user_id}

もし下記のようにパス/users/meがパス/users/{user_id}の後に宣言された場合、meという値がuser_idというパラメータとしてFastAPIに渡されてしまいます。

main_miss
from fastapi import FastAPI

app = FastAPI()


@app.get("/users/{user_id}")  # <- "/users/me"がGETメソッドできた場合、"me"という値が渡される
async def read_user(user_id: str):
    return {"user_id": user_id}


@app.get("/users/me")   # <- "/users/me"はすでに評価されたため、この関数は呼び出されない
async def read_user_me():
    return {"user_id": "the current user"}

Predefined values (定義済みの値)

パスパラメータを受けるPath Operaterがあり、そのパラメータの設定可能な有効値を事前に定義しておきたい場合、Pythonの標準モジュールEnumの使用をおすすめします。

まずはEnumクラスを作成してみましょう。

Enumをインポートし、strEnumを継承したサブクラスを作成します。
strを継承することで、APIドキュメントは設定される値が文字列型であることを事前に知ることができ、正しくレンダリングを行われるようになります。

次に、有効な値となる固定値を持ったクラス属性を作成します。

main
from enum import Enum   # <- Enumのインポート

from fastapi import FastAPI


class ModelName(str, Enum): # <- クラスの継承
    alexnet = "alexnet"     # <- クラス属性(固定値)の作成
    resnet = "resnet"
    lenet = "lenet"


app = FastAPI()

定義したクラスを用いてパスパラメータを定義していきます。

作成した列挙型クラス(ModelName)を使用し、型アノテーションを付与したパスパラメータを作成します。

main
from enum import Enum

from fastapi import FastAPI


class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"


app = FastAPI()


@app.get("/model/{model_name}")
async def get_model(model_name: ModelName): # <- 型アノテーションを付与したパスパラメータ
    if model_name == ModelName.alexnet:
        return {"model_name": model_name, "message": "Deep Learning FTW!"}

    if model_name.value == "lenet":
        return {"model_name": model_name, "message": "LeCNN all the images"}

    return {"model_name": model_name, "message": "Have some residuals"}

ドキュメント( http://127.0.0.1:8000/docs )を確認してください。
パスパラメータで使用できる値は事前に定義されているため、ドキュメントでそれらの値がインタラクティブに表示、選択されます。

  1. GETタブをクリックし、タブ上部のTry it outをクリックしてください。
  2. model_nameが選択可能になります。
  3. 値を選択してExecteをクリックすることでリクエストを発行できます。

(下記のスクリーンショットは公式ドキュメトからの引用になります。
私の環境では選択画面のスクリーンショットが上手く取得できませんでした。)

qiita_2_4 2020-07-13 23-00-54.png

列挙型について

main
from enum import Enum

from fastapi import FastAPI


class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"


app = FastAPI()


@app.get("/model/{model_name}")
async def get_model(model_name: ModelName):
    if model_name == ModelName.alexnet: # <- ①
        return {"model_name": model_name, "message": "Deep Learning FTW!"}  # <- ③

    if model_name.value == "lenet": # <- ②
        return {"model_name": model_name, "message": "LeCNN all the images"}    # <- ③ 

    return {"model_name": model_name, "message": "Have some residuals"}     # <- ③ 

詳細はPython公式ドキュメント enum --- 列挙型のサポートを参考にしてください。

ここでは、作成したModelNameの列挙型メンバーとの比較(①)、.valueによる列挙型の値の取得とその比較(②)、列挙型メンバーを返り値として使用(③)を行っています。
辞書型dictのようにネストになったJSONボディの場合でも、Path Operaterの値をenumメンバーとして返り値にすることが可能です。この場合、対応する値(上記ではstr)に変換されてクライアントに返されます。

インタラクティブドキュメントやブラウザでGETした場合、例えばこのようなJSONの返り値を取得できるでしょう。

{
  "model_name": "alexnet",
  "message": "Deep Learning FTW!"
}

パスを含むパスパラメータ

例えば、/files/{file_path}というパスを持つPath Operationがあるとします。
しかし、file_path自体が値としてパスを持っている場合(file_path = home/johndoe/myfile.txtのような場合)、
URLは次のようになるでしょう。

http://127.0.0.1:8000/files/home/johndoe/myfile.txt

OpenAPIでは、テストの定義と困難なシナリオの発生につながるという理由から、上記のようなパスを値として持つパスパラメータの宣言をサポートしていません。

しかしながら、Starletteの内部ツールを使用することで、FastAPIではそのようなパスパラメータを宣言することができます。
(ただし、OpenAPIに準拠するAPIドキュメントでパスパラメータ自体にパスが含まれていることを確認することはできません。※ドキュメント自体は動作します)

次のような URL を使用して、パスを含むパスパラメータを宣言します。

/files/{file_path:path}

上記では、パスパラメータはfile_pathであり、:pathの部分がパスパラメータにパスが含まれていることを明示しています。

使用例としては以下のようになります。

main
from fastapi import FastAPI

app = FastAPI()


@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

終わりに

次回はクエリパラメータになります。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?