LoginSignup
5
4

More than 1 year has passed since last update.

FastAPI と SQLModel

Last updated at Posted at 2021-12-01

(注意)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に過ぎないことです。

SQLModel - 公式サイト

サンプルコード

公式サイトのサンプルコードを掲載します。詳細は公式サイトに譲ります。十分に詳しい解説がありますので。
Simple Hero API with FastAPI - 公式サイト

main.py
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 がもちろん使えます。非常に便利ですね。簡単にテストしてみます。

初期画面です

メッソドが2つ認識されています
image.png

Hero Schemaです

ちゃんと Hero Schemaが認識されています
image.png

Postを実行します

image.png

実行結果です

同じようにして花子も作成しておきます
image.png

Getで全Heroを取得します

うまくいきました

image.png

一応、sqliteコマンドで中身を確認しておきます

全く問題ありませんね
image.png

SQLModelですが、使い勝手はとてもよさそうです。後は実績を積んでくれることだけですかね。
今回は以上です。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4