はじめに
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
として保存してください。
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_id
がPathパラメータであり、引数item_id
として関数の引数に渡されます。
例としてこのファイルを保存した後にサーバを起動(uvicorn main:app --reload
)し、 http://127.0.0.1:8000/items/foo をブラウザで開いてみてください。下記の通り、変数item_id
に文字列foo
が代入されたJSON形式のテキストが返ってくるはずです。
{"item_id":"foo"}
実際の画面
これがFastAPIにおけるPathパラメータの基本となります。
型付きのPathパラメータ
FastAPIではPython標準の型アノテーションを使用して、関数内でPathパラメータの型を宣言することができます。
~
# 前略
~
@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_id
をint
(整数型)として宣言しています。
型宣言によって、エラーチェックや単語の補完などのエディタサポートを受けられるようになるでしょう。(上記のコードでは、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
と表記された青いタブをクリックすれば展開されます。)
Parameters
のNameitem_id
の下にinteger
とありますね。Pythonの型宣言が(Swagger UIに統合された)インタラクティブなドキュメントにも反映されていることがわかるかと思います。
このドキュメントではAPIの様々な情報を得ることができ、また、デザイナーやフロントサイドの人々と共有することが可能です。
また、このドキュメントはFastAPIが生成したスキーマを元にしたものですが、生成されたスキーマはOpenAPIに準拠しており、様々な互換性のあるツールが開発されています。
メモその1でも確認したましたが、FastAPIではReDocを使用した代替ドキュメントも存在しています。
http://127.0.0.1:8000/redoc にアクセスしてみてください。
ざっくりとした個人的な印象ですが、Swagger UI は実際にリクエストを生成して結果の確認までできるインタラクティブ(対話的)なドキュメント、一方のReDocは静的でシンプルなドキュメントという認識です。おそらくもっと色々な使い方や別のツールがあるのでしょうが、勉強不足でまったくわかりません。すみません。学習次第、更新していきたいと思います。
今のところ、リクエストを生成して結果も見れるSwagger UIが便利だなぁという気持ちです。
Pydantic
本題に戻りまして、このようなドキュメント生成にも役立つ型アノテーションは、すべてPydanticというライブラリによって行われています。
Pydanticについては下記の記事が参考になりました。
Pydantic 入門 @0622okakyo
Pydanticにより、str
、float
、bool
など多くのデータ型で、すべて同様に型アノテーションを行うことができます。
Path Operationにおける順序問題
Path Operationを作成する場合、パスが固定されてしまう状況になることがあります。
例として、ユーザが自身のデータを取得するための/users/me
というパスがあるとします。
この時、同時にある別のユーザに関するデータをuser_id
を使用して取得する/users/{user_id}
というパスも存在するとしましょう。
FastAPIにおけるPath Operationは順番に評価されていきます。
よって、下記のようにパス/users/me
はパス/users/{user_id}
よりも前に宣言される必要があります。
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に渡されてしまいます。
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
をインポートし、str
とEnum
を継承したサブクラスを作成します。
str
を継承することで、APIドキュメントは設定される値が文字列型であることを事前に知ることができ、正しくレンダリングを行われるようになります。
次に、有効な値となる固定値を持ったクラス属性を作成します。
from enum import Enum # <- Enumのインポート
from fastapi import FastAPI
class ModelName(str, Enum): # <- クラスの継承
alexnet = "alexnet" # <- クラス属性(固定値)の作成
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
定義したクラスを用いてパスパラメータを定義していきます。
作成した列挙型クラス(ModelName
)を使用し、型アノテーションを付与したパスパラメータを作成します。
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 )を確認してください。
パスパラメータで使用できる値は事前に定義されているため、ドキュメントでそれらの値がインタラクティブに表示、選択されます。
-
GET
タブをクリックし、タブ上部のTry it out
をクリックしてください。 -
model_name
が選択可能になります。 - 値を選択して
Execte
をクリックすることでリクエストを発行できます。
(下記のスクリーンショットは公式ドキュメトからの引用になります。
私の環境では選択画面のスクリーンショットが上手く取得できませんでした。)
列挙型について
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
の部分がパスパラメータにパスが含まれていることを明示しています。
使用例としては以下のようになります。
from fastapi import FastAPI
app = FastAPI()
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}
終わりに
次回はクエリパラメータになります。