背景・目的
前回、Fast API(※下記の記事参照)について特徴を整理し簡単に試しました。
今回は、下記のチュートリアルを通して理解を深めたいと思います。
まとめ
下記に特徴を整理します。
特徴 | 説明 |
---|---|
Starlette | 軽量のASGIフレームワーク/ツールキットであり、Python で非同期 Web サービスを構築するのに最適。 |
ASGI | Asynchronous Server Gateway Interfaceの略 非同期対応のPython Webサーバ、フレームワーク、アプリケーション間の標準インターフェイスを提供することを目的としている WSGI が同期 Python アプリの標準を提供したのに対して、ASGI は WSGI 下位互換性実装と複数のサーバーおよびアプリケーション フレームワークを備え、非同期アプリと同期アプリの両方に標準を提供する |
Pydantic | Pydantic は、Python で最も広く使用されているデータ検証ライブラリ 高速で拡張可能な Pydantic は、linters/IDE/brainとうまく連携する |
概要
Recap, step by step
下記を基にまとめます。
Step 1: import FastAPI
from fastapi import FastAPI
FastAPI は、API のすべての機能を提供する Python クラスです。
FastAPI は Starlette から直接継承するクラスです。 Starlette のすべての機能を FastAPI でも使用できます。
Step 2: create a FastAPI "instance"
app = FastAPI()
app変数がFastAPIクラスのインスタンスになる。
すべてのAPIを作成するための主要なポイント
Step 3: create a path operation¶
Path
- ここでの「パス」とは、URL の最初の / から始まる最後の部分を指す
- URLは下記のようになる
https://example.com/items/foo
- パスは下記のようになる
/items/foo
- 「パス」は一般に「エンドポイント」または「ルート」とも呼ばれる
Operation
- ここでの「オペレーション」とは、HTTP の「メソッド」の 1 つを指す
- HTTP プロトコルでは、これらの「メソッド」の 1 つ (または複数) を使用して各パスと通信できる
- API を構築するときは、通常、これらの特定の HTTP メソッドを使用して特定のアクションを実行する
- 通常の使用法は下記の通り
- POST: to create data
- GET: to read data
- PUT: to update data
- DELETE: to delete data
- そのため、OpenAPI では、それぞれの HTTP メソッドを「オペレーション」と呼ぶ
- Define a path operation decorator
@app.get("/")
-
@app.get("/")
は、すぐ下の関数が次の宛先へのリクエストの処理を担当することを FastAPI に伝えている- path=
/
- getオペレーションを利用している
- path=
-
- その他のオペレーション
@app.post()
@app.put()
@app.delete()
@app.options()
@app.head()
@app.patch()
@app.trace()
- 各操作 (HTTP メソッド) は必要に応じて自由に使用できる。FastAPI は特定の意味を強制しない
- 上記は、こガイドラインとして提供されており、要件ではない
- たとえば、GraphQL を使用する場合、通常は POST 操作のみを使用してすべてのアクションを実行する
Step 4: define the path operation function
@app.get("/")
async def root():
- パス操作関数
- GET 操作を使用して URL「/」へのリクエストを受信するたびに、FastAPI によって呼び出される
- 非同期関数
-
async def
の代わりに通常の関数として定義も可能
@app.get("/")
def root():
Step 5: return the content
@app.get("/")
async def root():
return {"message": "Hello World"}
- 辞書、リスト、特異値を str、int などとして返すことが可能
- Pydantic モデルを返すこともできる
- 自動的に JSON (ORM などを含む) に変換されるオブジェクトやモデルは他にも多数ある
関連知識
Starlette
下記のページを基に特徴を整理します。
https://www.starlette.io/
Starlette は軽量のASGIフレームワーク/ツールキットであり、Python で非同期 Web サービスを構築するのに最適です。
本番環境ですぐに使用可能で、次の機能が提供されます。
- 軽量で複雑さの少ない HTTP Web フレームワーク。
- WebSocket サポート。
- インプロセスバックグラウンドタスク。
- 起動およびシャットダウン イベント。
- 上に構築されたテストクライアントhttpx。
- CORS、GZip、静的ファイル、ストリーミング応答。
- セッションと Cookie のサポート。
- 100% テストカバレッジ。
- 100% 型注釈付きのコードベース。
- ハード依存関係はほとんどありません。
- asyncioおよびバックエンドと互換性がありますtrio。
- 独立したベンチマークに対して全体的に優れたパフォーマンスを発揮します。
- Starlette
- 軽量のASGIフレームワーク/ツールキット
- Python で非同期 Web サービスを構築するのに最適
- 下記の機能を提供している
- 軽量で複雑さの少ない HTTP Web フレームワーク
- WebSocketをサポート
- In-process バックグラウンドタスク
- 起動およびシャットダウン イベント
- httpx上に構築されたテストクライアント
- CORS、GZip、静的ファイル、ストリーミングレスポンス
- セッションと Cookie のサポート
- 100% テストカバレッジ
- 100% アノテーション付きのコードベース
- ハード依存関係はほとんどなし
- asyncioおよびrioのバックエンドと互換性がある
- 独立したベンチマークに対して全体的に優れたパフォーマンスを発揮する
ASGI
下記を基に整理します。
- Asynchronous Server Gateway Interfaceの略
- 非同期対応のPython Webサーバ、フレームワーク、アプリケーション間の標準インターフェイスを提供することを目的としている
- WSGI が同期 Python アプリの標準を提供したのに対して、ASGI は WSGI 下位互換性実装と複数のサーバーおよびアプリケーション フレームワークを備え、非同期アプリと同期アプリの両方に標準を提供する
Pydantic
下記を基に整理します。
- Pydantic は、Python で最も広く使用されているデータ検証ライブラリ
- 高速で拡張可能な Pydantic は、linters/IDE/brainとうまく連携する
- Python 3.8 以降で動作する
Why use Pydantic?
- Powered by type hints — with Pydantic, schema validation and serialization are controlled by type annotations; less to learn, less code to write, and integration with your IDE and static analysis tools. Learn more…
- Speed — Pydantic's core validation logic is written in Rust. As a result, Pydantic is among the fastest data validation libraries for Python. Learn more…
- JSON Schema — Pydantic models can emit JSON Schema, allowing for easy integration with other tools. Learn more…
- Strict and Lax mode — Pydantic can run in either strict mode (where data is not converted) or lax mode where Pydantic tries to coerce data to the correct type where appropriate. Learn more…
- Dataclasses, TypedDicts and more — Pydantic supports validation of many standard library types including dataclass and TypedDict. Learn more…
- Customisation — Pydantic allows custom validators and serializers to alter how data is processed in many powerful ways. Learn more…
- Ecosystem — around 8,000 packages on PyPI use Pydantic, including massively popular libraries like FastAPI, huggingface, Django Ninja, SQLModel, & LangChain. Learn more…
- Battle tested — Pydantic is downloaded over 70M times/month and is used by all FAANG companies and 20 of the 25 largest companies on NASDAQ. If you're trying to do something with Pydantic, someone else has probably already done it. Learn more…
- Powered by type hints
- Pydantic では、スキーマ検証とシリアル化は型注釈によって制御されるため、学習する内容が少なくなり、記述するコードも少なくなり、IDE や静的解析ツールと統合される
- Speed
- Pydantic のコア検証ロジックは Rust で書かれている
- その結果、Pydantic は Python で最も高速なデータ検証ライブラリの 1 つとなっている
- JSON Schema
- Pydantic モデルは JSON スキーマを出力できるため、他のツールとの統合が容易になる
- Strict and Lax mode
- Pydantic は、厳密モード (データが変換されない) または緩いモード (適切な場合に Pydantic がデータを正しい型に強制変換しようとする) のいずれかで実行できる
- Dataclasses, TypedDicts and more
- Pydantic は、dataclassやを含む多くの標準ライブラリ型の検証をサポートしている
- Customisation
- Pydantic では、カスタム バリデータとシリアライザーを使用して、さまざまな強力な方法でデータの処理方法を変更できる
- Ecosystem
- PyPI 上の約 8,000 個のパッケージが Pydantic を使用している
- これには、 FastAPI、huggingface、Django Ninja、SQLModel、LangChainなどの非常に人気のあるライブラリが含まれる
- Battle tested
- Pydantic は毎月 7,000 万回以上ダウンロードされており、すべての FAANG 企業と NASDAQ の上位 25 社のうち 20 社で使用されている
実践
First Steps
下記を基に試します。
OpenAPI
-
openapi.pyというファイル名で下記の内容を書きます
from fastapi import FastAPI app = FastAPI() @app.get("/") async def root(): return {"message": "Hello World"}
-
起動します
fastapi dev openapi.py
-
ブラウザで
http://127.0.0.1:8000/
を開きます -
ブラウザで
http://127.0.0.1:8000/docs
を開きます
Check the openapi.json
OpenAIの基になる素のopenapi.jsonをファイルを確認します。
Path Parameters
Python フォーマット文字列で使用されるのと同じ構文を使用して、パスの「パラメータ」または「変数」を宣言できる
-
pathparameter.py
というファイルにコードを書きますfrom fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id): return {"item_id": item_id}
- アプリケーションを起動します
$ fastapi dev pathparameter.py
- ブラウザで
http://127.0.0.1:8000/items/foo
を開きます - 下記のように表示されました
Path parameters with types
標準の Python 型アノテーションを使用して、関数内のパス パラメーターの型を宣言できます。
-
pathparameter.py
のパラメータをint型に変更しますfrom fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id: int): return {"item_id": item_id}
-
ブラウザで
http://127.0.0.1:8000/items/foo
を開きます -
下記のように表示されました。intじゃなきゃだめと言われました
- これにより、関数内でエラー チェックや補完などのエディター サポートが提供される
Data conversion
-
ブラウザで、
http://127.0.0.1:8000/items/3
を開きます -
- 関数が受け取った (そして返した) 値は、文字列「3」ではなく、Python int としての 3 である
- その型宣言により、FastAPI は自動的にリクエストを「解析」する
Data validation
-
ブラウザで、
http://127.0.0.1:8000/items/foo
を開きます -
また、ブラウザで、
http://127.0.0.1:8000/items/4.2
を開きます
Pydantic
すべてのデータ検証は Pydantic によって内部で実行されるため、そのメリットをすべて享受できます。
str、float、bool、およびその他の多くの複雑なデータ型で同じ型宣言を使用できます。
Order matters
パス操作を作成するときに、パスが固定されている状況が発生することがある。
/users/me
と同様に、現在のユーザーに関するデータを取得するためだとしましょう。
また、パス /users/{user_id} を使用して、ユーザー ID によって特定のユーザーに関するデータを取得することもできます。 パス操作は順番に評価されるため、/users/me のパスが /users/{user_id} のパスより前に宣言されていることを確認する必要があります。
-
user.py
ファイルにコードを書きます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}
-
アプリケーションを起動します
fastapi dev user.py
-
ブラウザで、
http://127.0.0.1:8000/users/me
を開きます -
ブラウザで、
http://127.0.0.1:8000/users/100
を開きます
上記の場合、/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} # Added @app.get("/users") async def read_users(): return ["Rick","Morty"] # Added @app.get("/users") async def read_users2(): return ["Bean","Elfo"]
-
ブラウザで、
http://127.0.0.1:8000/users
を開きます
Predefined values
パス パラメーターを受け取るパス操作があるが、有効なパス パラメーター値を事前定義しておきたい場合は、標準の Python Enum を使用できます。
Create an Enum class
Enum をインポートし、str と Enum を継承するサブクラスを作成します。
str から継承することにより、API ドキュメントは値が文字列型である必要があることを認識し、正しくレンダリングできるようになります。
次に、利用可能な有効な値となる固定値を持つクラス属性を作成します。
-
enum_test.py
ファイルにコードを書きます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 == 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"}
-
アプリケーションを起動します
fastapi dev enum_test.py
-
ブラウザで、
http://127.0.0.1:8000/models/XXXXX
を開きます。事前定義されてないのでエラーになりました
Declare a path parameter
次に、作成した enum クラス (ModelName) を使用して、型アノテーションを持つパス パラメーターを作成します。
Check the docs
path パラメーターに使用できる値は事前に定義されているため、インタラクティブなドキュメントで適切に表示できます。
-
ブラウザで、
http://127.0.0.1:8000/docs#/default/get_model_models__model_name__get
を開きます
-
「Try it out」をクリックします
Path parameters containing paths
パス /files/{file_path} を使用したパス操作があるとします。
ただし、home/johndoe/myfile.txt のように、file_path 自体にパスを含める必要があります。 したがって、そのファイルの URL は /files/home/johndoe/myfile.txt のようになります。
OpenAPI support
OpenAPI は、内部にパスを含むパス パラメーターを宣言する方法をサポートしていません。これは、テストと定義が困難なシナリオにつながる可能性があるためです。 それでも、Starlette の内部ツールの 1 つを使用して、FastAPI でこれを行うことはできます。
また、パラメータにパスを含める必要があることを示すドキュメントは追加されませんが、ドキュメントは引き続き機能します。
Path convertor
Starlette から直接オプションを使用すると、次のような URL を使用してパスを含むパス パラメータを宣言できます。
/files/{file_path:path}
この場合、パラメータの名前は file_path で、最後の部分 :path は、パラメータが任意のパスに一致する必要があることを示します。
-
filepath.py
に下記のコードを書きますfrom fastapi import FastAPI app = FastAPI() @app.get("/files/{file_path:path}") async def read_file(file_path: str): return {"file_path": file_path}
- アプリケーションを起動します
fastapi dev filepash.py
- ブラウザで、
http://127.0.0.1:8000/files/files/home/johndoe/myfile.txt
を開きます
Query Parameters
パス パラメーターの一部ではない他の関数パラメーターを宣言すると、それらは自動的に「クエリ」パラメーターとして解釈されます。
クエリは、? の後に続くキーと値のペアのセットです。 URL 内で & 文字で区切ります。
-
ファイル
query_parameters.py
に、下記のコードを書きますfrom fastapi import FastAPI app = FastAPI() fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] @app.get("/items/") async def read_item(skip: int = 0, limit: int = 10): return fake_items_db[skip : skip + limit]
- クエリパラメータは次のとおりです
- skip: 値が 0
- limit: 値は 10
- パス パラメーターに適用されたのと同じプロセスがクエリ パラメーターにも適用されます
- クエリパラメータは次のとおりです
-
アプリケーションを起動します
fastapi dev query_parameters.py
-
次にクエリパラメータを含めて
http://127.0.0.1:8000/items/?skip=0&limit=2
を開きます。2県表示されました
Optional parameters
同様に、デフォルトを None に設定することで、オプションのクエリ パラメータを宣言できます。
-
option_parameters.py
に、下記のコードを書きますfrom fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id: str, q: str = None): if q: return {"item_id": item_id, "q": q} return {"item_id": item_id}
- この場合、関数パラメータ q はオプションであり、デフォルトでは None になります
-
アプリケーションを起動します
fastapi dev option_parameters.py
Query parameter type conversion
bool 型を宣言することもでき、それらは変換されます。
-
option_parameters.py
のコードを修正しますfrom fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id: str, q: str | None = None, short: bool = False): item ={"item_id": item_id} if q: item.update({"q": q}) if not short: item.update({"description": "This is an amazing item that has a long description"}) return item
Multiple path and query parameters
複数のパス パラメーターとクエリ パラメーターを同時に宣言でき、FastAPI はどれがどれであるかを認識します。 また、特定の順序で宣言する必要はありません。
これらは名前によって検出されます。
-
multiple_path_query_parameters.py
ファイルに下記のコードを書きますfrom fastapi import FastAPI app = FastAPI() @app.get("/users/{user_id}/items/{item_id}") async def read_user_item( user_id: int ,item_id: str ,q: str| None = None , short: bool = False ): item = {"item_id": item_id, "owner_id": user_id} if q: item.update({"q": q}) if not short: item.update( {"description": "This is an amazing item that has a long description"} ) return item
- アプリケーションを起動します
fastapi dev multiple_path_query_parameters.py
- ブラウザで、
http://127.0.0.1:8000/users/1/items/item1000
を開きます
Required query parameters
非パス パラメーター のデフォルト値を宣言する場合、それは必要ありません。
特定の値を追加せずにオプションにするだけの場合は、デフォルトを「なし」に設定します。
ただし、クエリ パラメータを必須にしたい場合は、デフォルト値を宣言しないだけで済みます。
-
required_query_parameters.py
を作成し、下記のコードを書きますfrom fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id: str, needy: str): item = {"item_id": item_id, "needy": needy} return item
-
ここで、クエリ パラメータ needy は、str 型の必須クエリ パラメータです。 ブラウザで
http://127.0.0.1:8000/items/foo-item
を開くと、必要なパラメータを追加しないと、次のようなエラーが表示されます
-
needy は必須パラメータであるため、URL に設定する必要があります。
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
を開きます
必要に応じていくつかのパラメータを定義できます。いくつかはデフォルト値を持つもので、いくつかは完全にオプションです。
-
required_query_parameters.py
を修正しますfrom fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_user_item( item_id: str, needy: str, skip: int = 0, limit: int | None = None ): item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} return item
- クエリ パラメーターは 3 つあります
- needy、必須の str
- Skip、デフォルト値が 0 の int
- kimit、オプションの int
-
ブラウザで、
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
を開きます(skipはデフォルトの0が含まれている) -
ブラウザで、
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy&skip=10
を開きます
考察
リクエストパスや、クエリストリングなどの扱いを試しました。今後も継続してチュートリアルを進めます。
参考