🐎
FastAPIを使ってみた
なんの記事?
FastAPI を試しに使ってみた記録 & 感想
FastAPI って?
FastAPI は、Python の標準的な型ヒントをベースに
Python で API を構築するためのモダンで高速(高性能)な Web フレームワーク
開発環境
| 項目 | 名前 |
|---|---|
| PG | Python v3.11 |
| FW | FastAPI v0.103 |
| DB | PostgreSQL latest |
| パッケージ管理 | uv |
| ORM | SQLAlchemy |
| ASGI サーバー | uvicorn |
ASGI?uvicorn なんぞ?
ASGI(Asynchronous Server Gateway Interface)は、非同期の Web と App のインターフェイス
- HTTP のやり取りを非同期でするためのインターフェイス
- WSGI の後継らしい
- WSGI(Web Server Gateway Interface)は、Perl の CGI(Common Gateway Interface)の Web 版
ASGI のひとつに uvicorn という ASGI サーバがある
- ほかには、daphne や hypercorn などがある (らしい)
- gunicorn は WSGI サーバ
簡単な書き方まとめ
FastAPI は、デコレータでルートを定義し、関数で処理を記述する方式
GET /health
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "ok"}
GET /items/{item_id}
path パラメータはテンプレート文字列で指定し、関数の引数で受け取る
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
GET /items/{item_id}?q=search
クエリパラメータは関数の引数で受け取る
パスとクエリで同名は使えないので注意
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
POST /items
リクエストボディは Pydantic モデルで定義し、関数の引数で受け取る
- PUT/PATCH/DELETE など他 HTTP メソッドも
@app.xxxデコレータで指定すれば OK
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
@app.post("/items")
async def create_item(item: Item):
return {"item": item}
ハマったポイント
300 番台のステータスコードが返ってくる
下記で実装したところ、なぜか 300 番台のステータスコードが返ってきた
- 原因: 末尾に
/が付いていると 300 番台のステータスコードが返ってくる - 解決: 末尾の
/を削除する - 末尾のスラッシュは、トレーリングスラッシュというらしい
問題のコード
@app.get("/health/")
async def health():
return {"status": "ok"}
SQL 検索時に、テーブル名が異なる
FastAPI で SQLAlchemy を使っているときに、SQL 検索時にテーブル名が異なるという問題が発生した
- 原因: SQLAlchemy の
SQLModelを使うと、クラス名をテーブル名として使用する - 解決方法 1: クラス名をテーブル名に合わせて命名する
- 解決方法 2:
__tablename__を指定する
問題のコード
class ShopItem(SQLModel, table=True):
item_id: str = Field(max_length=100)
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
item = session.get(ShopItem, item_id)
return item
# > select * from **ShopItem** where id = 1;
解決方法1 クラス名をテーブル名に合わせて命名する
class shop_item(SQLModel, table=True):
item_id: str = Field(max_length=100)
解決方法2 tablenameを指定する
class ShopItem(SQLModel, table=True):
__tablename__ = "shop_item"
item_id: str = Field(max_length=100)
感想
Flask と同じような感覚かつ、Flaskよりも型安全に書けた。
ORMの恩恵なのかFastAPI恩恵なのか切り分けしきれてないので、もう少し使い込む必要がありそう。
あとPythonで、VSCode と venv (インタープリンタ) の連携がうまくいかず、毎回仮想環境をアクティベートするのが面倒だった。
なぜインポートエラー???
※有識者教えてください
Discussion