はじめに
FastAPI は Pydantic によるリクエスト・レスポンスのバリデーション、DI、バックグラウンドタスクの実行、OpenAPIとしてのドキュメントの公開など、便利な機能が満載なフレームワークになっています。
そんな FastAPI で更に便利に開発するべく、様々な OSS の拡張ライブラリが実装されています。
FastAPI 拡張ライブラリは OR Mapper や、認証サービス連携などは特に多い印象なのですが、そちらは既に Qiita 記事もいくつか存在しているので割愛させていただくこととします。
本記事ではこれまであまり紹介されていない開発をちょっと便利にするようなライブラリについていくつか紹介します。
fastapi-camelcase
世の中の WebAPI では、リクエストやレスポンスのフィールドはキャメルケースの命名であることが多いと思います。
一方で Python ではクラスのメンバーはスネークケースで記述することが一般的であるため、Pydantic Model でスキーマを定義する場合に悩む場合も多いのではないでしょうか?
そんなときに使用すると便利なのが fastapi-camelcase です。
使用方法は簡単で、 pytantic.BaseModel
の代わりに fastapi_camelcase.CamelModel
を継承してスキーマを実装します。
from fastapi_camelcase import CamelModel
class User(CamelModel):
first_name: str
last_name: str
age: int
このモデルを使って FastAPI アプリケーションを起動して OpenAPI ドキュメントページ(/doc)を開き、スキーマ定義の欄を確認すると、以下のようになっていることを確認できると思います。
first_name
, last_name
が それぞれキャメルケースに変換されていますね。
Response Users object
firstName string
lastName string
age integer
このライブラリの裏側の仕組みとしては、完全に Pydantic の機能をラップする形で実現されています。
Pydantic v2 においては、 model_config に alias_generator
として、 alias_generators.to_camel
を挿入することで、フィールド名のキャメルケースバージョンをエイリアスとして指定できるようになります。
加えてエイリアスとして定義された名前をオブジェクト作成時に入力することを許容するために、 populate_by_name
を True に設定することで CamelModel
を実現しています。
from pydantic import BaseModel, ConfigDict
from pydantic import alias_generators
class CamelModel(BaseModel):
model_config = ConfigDict(
alias_generator=alias_generators.to_camel,
populate_by_name=True
)
pydantic だけで実現できるとはいえ、このあたりの仕組みを詳しく知らずとも継承だけで変換できるのはお手軽ですね。
CamelModelの継承先で model_config の alias_generator
や pupulate_by_name
の設定を変更してしまうと、キャメルケース変換ができなくなってしまうので注意が必要です。
fastapi-pagination
リストを取得するエンドポイントを実装する際、常に全件取得するような設計にしてしまうと何かとパフォーマンスの問題を抱えることになるので、ページネーションなどを設けることがよくあると思います。
fastapi-pagination を使用すると、ページネーションの実装を非常に簡単に導入することができます。
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi_pagination import Page, add_pagination, paginate
app = FastAPI()
class Person(BaseModel):
name: str
persons = [
Person(name="taro"),
Person(name="jiro"),
Person(name="saburo"),
Person(name="shiro"),
Person(name="goro"),
]
@app.get("/persons", response_model=Page[Person])
async def get_persons():
return paginate(persons)
app = add_pagination(app)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
キモになるのは、エンドポイントのレスポンスモデルに Page[T]
クラスを設定している点と、FastAPI
インスタンスを add_pagination()
でラップしている点、そしてエンドポイントのレスポンスを返すときに、 paginate
メソッドを実行している点です。
Page[T]
クラスを response_model
に指定すると、ページングのためのクエリパラメータ "page" と "size" が自動的にエンドポイントのスキーマに追加されます。
この仕組みのために add_pagination()
wrapper を実行する必要があります。
最後に paginate()
メソッドでは実際にレスポンスのデータにページング加工を施します。
上記のコード例を見て「これではメモリに一旦リストを全件持ってきているじゃないか」と思われた方もいらっしゃると思います。
fastapi-pagination にはインメモリデータのページング処理以外にも、 SQLAlchemy をはじめとしたデータベースフレームワークを介してページネーションを実現するためのバージョンも豊富に存在しているので、アプリケーションで使用しているデータベースに合わせたページネーション処理を使用することも、独自にページネーション処理を追加することも可能です。
from sqlalchemy import select
from pydantic import BaseModel
from fastapi import Depends
from fastapi_pagination.ext.sqlalchemy import paginate # SQLAlchemy 用の paginate 関数に変更
def get_db():
...
@app.get('/users', response_model=Page[Person])
def get_persons(db: Session = Depends(get_db)):
return paginate(db, select(Person).order_by(Person.created_at))
msgpack-asgi
FastAPI では JSON 形式でシリアライズ・デシリアライズして通信することが主ですが、非常に大きなデータを通信する場合そのオーバーヘッドが気になってきます。そのような場合により効率的なデータフォーマットとして MessagePack のような形式が存在します。
msgpack-asgi を使用すると、FastAPI のコンテンツネゴシエーションを自動的に MessagePack(applications/x-msgpack) に変換してくれます。
使い方としては FastAPI に Messagepack 用の Middleware を追加するだけでお手軽に実装できます。
from fastapi import FastAPI
from msgpack_asgi import MessagePackMiddleware
app = FastAPI()
app.add_middleware(MessagePackMiddleware)
これだけでメッセージの転送帯域を小さくすることができます。
その代わりに、 Messagepack 形式のメッセージをデコードする際に CPU コストが JSON に比べ大きくなります。
どちらを優先すべきかはユースケースに依存するので検討が必要です。
asgi-correlation-id
FastAPI のような ASGI アプリケーションに相関 ID の読み取り/生成を行うミドルウェアを提供します。
相関 ID は、マイクロサービスのような分散システム内の処理を追跡したり、アプリケーションのログを設計する際に、そのログの一連の流れを追うために役に立ちます。
from asgi_correlation_id import CorrelationIdMiddleware
from fastapi import FastAPI
app = FastAPI()
app.add_middleware(CorrelationIdMiddleware)
この設定を加えることで、リクエストヘッダに設定された "X-Request-ID" から、外部の相関 ID 読み取って連携したり、それが無い場合には相関 ID を生成してレスポンスヘッダに返却することができるようになります。
fastapi_profiler
fastapi_profiler は、名前の通り FastAPI のエンドポイントのパフォーマンスプロファイラーを提供します。
専用の Middleware を追加するだけですぐに使用することができます。
from fastapi import FastAPI
from fastapi_profiler import PyInstrumentProfilerMiddleware
app = FastAPI()
app.add_middleware(PyInstrumentProfilerMiddleware)
この状態でアプリケーションを起動すると、各種エンドポイントを呼び出したときに、コンソールにプロファイリング結果がコンソールログに表示されます。
INFO: 127.0.0.1:58274 - "GET /users HTTP/1.1" 200 OK
Method: GET, Path: /users, Duration: 0.18209500000000034, Status: 200
_ ._ __/__ _ _ _ _ _/_ Recorded: 13:18:56 Samples: 1057
/_//_/// /_\ / //_// / //_'/ // Duration: 0.182 CPU time: 0.141
/ _/ v4.6.1
Program: example.py
fastapi_profiler のプロファイリングは pyinstrument というツールによって行われており、出力結果もこのツールによるものになります。
プロファイリング結果のファイル出力
プロファイリング結果を外部ファイルに出力して CI 結果などとして公開・共有したいというケースもあると思います。
そのような場合には Middleware 追加時にオプションを変更することで、測定結果をファイル出力することも可能です。
from fastapi import FastAPI
from fastapi_profiler import PyInstrumentProfilerMiddleware
app = FastAPI()
app.add_middleware(
PyInstrumentProfilerMiddleware,
server_app=app,
profiler_output_type="html",
html_file_name="example.html",
)
htmlファイルは FastAPI アプリケーションが終了したときに、 html_file_name
で指定したファイルパスに出力されます。
manage-fastapi
FastAPI アプリケーション開発時に使えるコマンドラインツールになります。
manage-fastapi をインストールすると、 fastapi
コマンドを使えるようになり
- プロジェクトの自動新規作成: startproject
- コンポーネントの自動新規作成: startapp
- アプリケーションの起動: run
といった操作がコマンドから実行可能になります。
$ fastapi startproject example-proj --interactive
特にプロジェクトの新規作成時には、ライセンスの選択、pythonバージョンの選択、Docker環境の使用有無、DB などを選択することで、設定にあったREADME、LICENSE、Dockerfile、ソースコードテンプレートなどが自動生成することができます。
このツールを使うことでプロジェクト開発初期のボイラープレートをほとんどそのまま使いまわすことができるため、開発効率の向上が見込めます。
おわりに
FastAPI による開発を更に便利にするための OSS ライブラリ・ツールを限定的ではありますが紹介させていただきました。
本記事で紹介したもの以外にも、まだまだ数多くの便利ライブラリが存在しますので、興味がある方はぜひ探して使ってみてください。
ここまでお付き合いいただきありがとうございました。