パスパラメータ¶
Python のフォーマット文字列と同じ構文で、パスの「パラメータ」または「変数」を宣言できます。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
パスパラメータ `item_id` の値は、引数 `item_id` として関数に渡されます。
したがって、この例を実行し、http://127.0.0.1:8000/items/foo にアクセスすると、次の応答が表示されます。
{"item_id":"foo"}
型を持つパスパラメータ¶
標準のPython型アノテーションを使用して、関数内でパスパラメータの型を宣言できます。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
この場合、`item_id` は `int` として宣言されています。
確認
これにより、関数内でエディタのサポートが得られ、エラーチェック、補完などが可能になります。
データの変換¶
この例を実行し、ブラウザでhttp://127.0.0.1:8000/items/3を開くと、次の応答が表示されます。
{"item_id":3}
確認
関数が受け取って返した値が、文字列 `"3"` ではなく、Python の `int` 型の `3` であることに注目してください。
このように型を宣言することで、**FastAPI** は自動的にリクエストの「パース」を行います。
データ検証¶
しかし、ブラウザで http://127.0.0.1:8000/items/foo にアクセスすると、次のようなHTTPエラーが表示されます。
{
"detail": [
{
"type": "int_parsing",
"loc": [
"path",
"item_id"
],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
"url": "https://errors.pydantic.dev/2.1/v/int_parsing"
}
]
}
パスパラメータ `item_id` の値が `"foo"` であり、これは `int` ではないためです。
http://127.0.0.1:8000/items/4.2 のように `int` の代わりに `float` を指定した場合も同様のエラーが発生します。
確認
つまり、同じPythonの型宣言で、**FastAPI**はデータ検証を提供します。
エラーメッセージは、バリデーションが失敗した箇所を正確に示していることにも注目してください。
これは、API と連携するコードを開発およびデバッグする際に非常に役立ちます。
ドキュメント¶
ブラウザで http://127.0.0.1:8000/docs を開くと、次のような自動でインタラクティブな API ドキュメントが表示されます。
確認
繰り返しになりますが、同じPython型宣言だけで、**FastAPI**は自動でインタラクティブなドキュメント(Swagger UIを統合)を提供します。
パスパラメータが整数型として宣言されていることに注目してください。
標準ベースの利点、代替ドキュメント¶
そして、生成されるスキーマはOpenAPI標準に準拠しているため、多くの互換性のあるツールが存在します。
このため、**FastAPI**自体は代替のAPIドキュメント(ReDocを使用)を提供しており、http://127.0.0.1:8000/redocでアクセスできます。
同様に、多くの互換性のあるツールがあります。これには、多くの言語用のコード生成ツールが含まれます。
Pydantic¶
すべてのデータ検証は Pydantic によって内部的に実行されるため、そのすべてのメリットを享受できます。そして、あなたは信頼できる手元にいることを知っています。
`str`、`float`、`bool`、および他の多くの複雑なデータ型でも同じ型宣言を使用できます。
これらの一部は、チュートリアルの次の章で説明されています。
順序が重要¶
パスオペレーションを作成する際、固定パスを持つ状況に遭遇することがあります。
たとえば、`/users/me` は現在のユーザーに関するデータを取得するためだとします。
そして、特定のユーザーIDで特定のユーザーに関するデータを取得するために、`/users/{user_id}` というパスを持つこともできます。
パス操作は順序通りに評価されるため、`/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/{user_id}` のパスも `/users/me` にマッチしてしまい、「me」という値を持つ `user_id` パラメータを受け取っていると「勘違い」してしまいます。
同様に、パス操作を再定義することはできません。
from fastapi import FastAPI
app = FastAPI()
@app.get("/users")
async def read_users():
return ["Rick", "Morty"]
@app.get("/users")
async def read_users2():
return ["Bean", "Elfo"]
パスが最初に一致するため、常に最初のものが使用されます。
事前定義された値¶
パスパラメータを受け取るパス操作があり、利用可能なパスパラメータの値を事前に定義したい場合は、標準のPythonの`Enum`を使用できます。
Enum
クラスを作成する¶
`Enum` をインポートし、`str` と `Enum` を継承するサブクラスを作成します。
`str` を継承することで、API ドキュメントは値が `string` 型である必要があることを認識し、正しくレンダリングできるようになります。
次に、固定値を持つクラス属性を作成します。これらが利用可能な有効な値になります。
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is 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"}
ヒント
もし疑問に思っているなら、「AlexNet」「ResNet」「LeNet」は単に機械学習モデルの名前です。
パスパラメータを宣言する¶
次に、作成したEnumクラス (`ModelName`) を型アノテーションとして使用して、パスパラメータを作成します。
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is 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` の列挙メンバーと比較できます。
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is 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"}
列挙値を取得する¶
`model_name.value` または一般的に `your_enum_member.value` を使用して、実際の値 (この場合は `str`) を取得できます。
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is 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"}
ヒント
`ModelName.lenet.value` を使用して値 `lenet` にアクセスすることもできます。
列挙メンバーを返す¶
JSON ボディ (例: `dict`) にネストされている場合でも、パス操作からenum メンバーを返すことができます。
それらはクライアントに返される前に、対応する値(この場合は文字列)に変換されます。
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is 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"}
クライアントでは、次のようなJSON応答が得られます。
{
"model_name": "alexnet",
"message": "Deep Learning FTW!"
}
パスを含むパスパラメータ¶
パスが `/files/{file_path}` のパス操作があるとします。
しかし、`file_path` 自体に `home/johndoe/myfile.txt` のようなパスが含まれている必要があります。
したがって、そのファイルのURLは、`/files/home/johndoe/myfile.txt` のようになります。
OpenAPI のサポート¶
OpenAPI は、パスパラメータにパスを含める方法をサポートしていません。それは、テストや定義が困難なシナリオにつながる可能性があるためです。
それでも、Starletteの内部ツールの一つを使用すれば、**FastAPI**でそれを行うことができます。
そして、パラメータがパスを含むべきであることを示すドキュメントは追加されませんが、ドキュメントは引き続き機能します。
パスコンバータ¶
Starlette のオプションを直接使用して、次のような 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}
ヒント
パラメータにスラッシュ (/
) で始まる /home/johndoe/myfile.txt
を含める必要がある場合があります。
その場合、URLは `files` と `home` の間に二重スラッシュ (//
) を含む `files//home/johndoe/myfile.txt` となります。
まとめ¶
**FastAPI** を使用すると、短く直感的で標準的な Python の型宣言を使うだけで、次のものが得られます。
- エディターのサポート: エラーチェック、オートコンプリートなど
- データ「パース」
- データ検証
- APIのアノテーションと自動ドキュメント
そして、それらを一度だけ宣言すればよいのです。
これはおそらく、**FastAPI**が他の代替フレームワークと比較して、(生のパフォーマンスは別として) 主な目に見える利点でしょう。