FastAPI と SQLModel

**（注意）2022/08/30　追加
この記事の上位版である新記事を以下に投稿しました。お手数ですが新記事の方を参照して頂ければと思います。
[FastAPI SQLModel 入門 - Qiita](https://qiita.com/sand/items/f338e67e86d728f793c2)**



**Tortoise ORM** は asyncio 互換のORM (Object Relational Mapper)です（inspired by Django）。
FastAPIでよく使われています。しかしFastAPIで使用するときは**Pydantic**である必要があり、Tortoise から Pydantic へ変換して使ったりします。つまり同じようなクラスを2重に持つことになります。この辺は、以下の過去記事に記してあります。

**【過去記事】**
[Tortoise ORM入門 - Qiita](https://qiita.com/sand/items/074866157635b50eb447)
[FastAPI と Tortoise ORM と Pydantic について - Qiita](https://qiita.com/sand/items/e5116ad4a476c654673a)


以上の問題を解決するために作られたのが **SQLModel** です。FastAPIの作者自らが作成しています。彼は**SQLModelクラスモデルは、SQLAlchemy モデルであると同時にPydantic モデルである**、と言っています。つまり**SQLModel**は、ORMとしては**SQLAlchemy** として扱え、そのままFastAPIの**path operation function**では**Pydantic**として使えるということです。冗長な**Tortoise ORM**はもう要りません。ただ唯一の不安はSQLModelはできたばかりで、まだ0.0.4に過ぎないことです。







[SQLModel - 公式サイト](https://sqlmodel.tiangolo.com/)


# サンプルコード
公式サイトのサンプルコードを掲載します。詳細は公式サイトに譲ります。十分に詳しい解説がありますので。
[Simple Hero API with FastAPI - 公式サイト](https://sqlmodel.tiangolo.com/tutorial/fastapi/simple-hero-api/)

```python: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として定義されています。

```python:
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**として使われています。


```python:
@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](https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/)


```
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](https://fastapi.tiangolo.com/advanced/events/)


```
@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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/b93c750a-ec58-e83d-ebb6-bf7cfd8de211.png)

## Hero Schemaです
ちゃんと Hero Schemaが認識されています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/086687b6-ae96-6e38-af73-67f6501dd127.png)


## Postを実行します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/d9cc8138-b2a5-b06e-8923-c2323a511697.png)

## 実行結果です
同じようにして花子も作成しておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/cc3076a3-2704-3eca-2120-d020178a2b6e.png)




## Getで全Heroを取得します
うまくいきました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/eddc8ade-18f8-cae9-1bf6-999d6599d22a.png)


## 一応、sqliteコマンドで中身を確認しておきます
全く問題ありませんね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/70cb79a1-986c-70fb-27f8-0a224078b375.png)



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