🐎

FastAPIを使ってみた

に公開

なんの記事?

FastAPI を試しに使ってみた記録 & 感想

FastAPI って?

FastAPI は、Python の標準的な型ヒントをベースに
Python で API を構築するためのモダンで高速(高性能)な Web フレームワーク
https://fastapi.tiangolo.com/ja

開発環境

項目 名前
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"}

https://wa3.i-3-i.info/word11910.html

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