(注意)2022/08/30 追加
この記事の上位版である新記事を以下に投稿しました。お手数ですが新記事の方を参照して頂ければと思います。
FastAPI SQLModel 入門 - Qiita
Tortoise ORM は asyncio 互換のORM (Object Relational Mapper)です(inspired by Django)。
FastAPIでよく使われています。しかしFastAPIで使用するときはPydanticである必要があり、Tortoise から Pydantic へ変換して使ったりします。つまり同じようなクラスを2重に持つことになります。この辺は、以下の過去記事に記してあります。
【過去記事】
Tortoise ORM入門 - Qiita
FastAPI と Tortoise ORM と Pydantic について - Qiita
以上の問題を解決するために作られたのが SQLModel です。FastAPIの作者自らが作成しています。彼はSQLModelクラスモデルは、SQLAlchemy モデルであると同時にPydantic モデルである、と言っています。つまりSQLModelは、ORMとしてはSQLAlchemy として扱え、そのままFastAPIのpath operation functionではPydanticとして使えるということです。冗長なTortoise ORMはもう要りません。ただ唯一の不安はSQLModelはできたばかりで、まだ0.0.4に過ぎないことです。
サンプルコード
公式サイトのサンプルコードを掲載します。詳細は公式サイトに譲ります。十分に詳しい解説がありますので。
Simple Hero API with FastAPI - 公式サイト
from typing import Optional
from fastapi import FastAPI
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
app = FastAPI()
@app.on_event("startup")
def on_startup():
create_db_and_tables()
@app.post("/heroes/")
def create_hero(hero: Hero):
with Session(engine) as session:
session.add(hero)
session.commit()
session.refresh(hero)
return hero
@app.get("/heroes/")
def read_heroes():
with Session(engine) as session:
heroes = session.exec(select(Hero)).all()
return heroes
ここで注目すべきなのがHeroクラスです。Heroは以下のようにSQLModelとして定義されています。
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
他方で、Heroクラスはpath operation functionではPydanticとして使われています。
@app.post("/heroes/")
def create_hero(hero: Hero):
with Session(engine) as session:
session.add(hero)
session.commit()
session.refresh(hero)
return hero
Heroクラスは一回定義するだけで、SQLAlchemy とPydanticの両方で使えています。
補足2022/07/25
もうすこしSQLModelに関する補足説明を加えます。
Create a Table with SQLModel - Use the Engine
engine = create_engine(sqlite_url, echo=True, connect_args=connect_args)
ここではSQLAlchemy Engineを作成しています。engineはdatabaseとのコミュニケーションをハンドルするためのものです。もしPostgreSQL や MySQLのようなdatabase serverを扱っているのなら、このengineはサーバへのネットワーク接続を含みます。echo=Trueはdatabaseへのアクセスの履歴をコマンドラインに出力することを指示します。
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
SQLModel.metadata.create_all(engine)はdatabaseを作成し、更にテーブルも作成してくれます。Hero Modelの定義でtable=Trueを指定していることに注目してください。この指定により、Modelに対するdatabase tabel作成します。この指定が無ければ対応するtableは存在しないことを意味します。
@app.on_event("startup")
アプリケーションが起動する前に実行したいコードは、startupイベントに関連させて記述します。
Events: startup - shutdown
@app.post("/heroes/")
def create_hero(hero: Hero):
with Session(engine) as session:
session.add(hero)
session.commit()
session.refresh(hero)
return hero
@app.get("/heroes/")
def read_heroes():
with Session(engine) as session:
heroes = session.exec(select(Hero)).all()
return heroes
ここではwithコンテキストを使って、一つのリクエスト毎に、一つのセッションが割り当てられます。
実行結果
FastAPIなのでSwagger UI がもちろん使えます。非常に便利ですね。簡単にテストしてみます。
初期画面です
Hero Schemaです
Postを実行します
実行結果です
Getで全Heroを取得します
うまくいきました
一応、sqliteコマンドで中身を確認しておきます
SQLModelですが、使い勝手はとてもよさそうです。後は実績を積んでくれることだけですかね。
今回は以上です。