SQLModelを試してみた

#SQLModelとは
<a href="https://sqlmodel.tiangolo.com/">公式はこちら</a>

現在PythonのWebFrameworkで最も注目されている(?)FastAPIの作者tiangolo氏による新しいPythonのORMである。
###特徴
Pythonの型付けを強力にサポートするPydanticと、有名なORMのSQLAlchemyをベースにラップしたもので、直感的で短いコードが書けてSQLAlchemyとの互換性も優れているようで、平たく言うと<b>今までデータベースのスキーマをPydanticで定義していたが、SQLAlchemyとFastAPIを使う場合は不要になった</b>ということ(だろう)。

早速試してみました。

---


環境　Ubuntu20.04　Pipenv　Python3.8.10　MySQL8.0　VScode


###インストール

```linux
pipenv install fastapi sqlmodel uvicorn
```
とやるも...


```
Creating a Pipfile for this project...
Installing fastapi...
Adding fastapi to Pipfile's [packages]...
✔ Installation Succeeded 
Installing sqlmodel...
Adding sqlmodel to Pipfile's [packages]...
✔ Installation Succeeded 
Installing uvicorn...
Adding uvicorn to Pipfile's [packages]...
✔ Installation Succeeded 
Pipfile.lock not found, creating...
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✘ Locking Failed! 
```

ぐぬぬ。。。依存関係の問題でLockfileを作れていない。。。

```
Hint: try $ pipenv lock --pre if it is a pre-release dependency.
```
と、あるので

```
/dev/python_code$  pipenv lock --pre
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success! 
Updated Pipfile.lock (d6534a)!
```
できた！

##FastAPIによりサーバを起動。


```python
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}
```

からのコマンド
```uvicorn main:app --reload
```

```
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [3921] using statreload
INFO:     Started server process [3923]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
```

サーバ起動を確認。(実はuvicornのエラーが出たが、pipenv shell で解決)


##DBに接続する

MySQLを用意するところは割愛します。私はAWSのEC2・linuxインスタンスにMySQLを立てています。接続情報が一部異なるだけで、ローカルで立てても公式通りにsqliteを使用しても問題ないです。

ここからは公式に沿ってSQLModelを使用してみます。
以下のカラムを定義します。

・id(int)
・name(str)
・nick_name(str)
・age(int)

データを以下のように格納したいとします。

|id|name|nick_name|age|
|:---:|:---:|:---:|:---:|
|1|朝倉未来|路上の伝説|29|
|2|朝倉海|海ぴょん|28|
|3|齋藤光|シバター|36|


まずはスキーマを定義します。

```python
from typing import Optional
from sqlmodel import Field, SQLModel

class fighters(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None
```

Python3.6より導入された型ヒントが用いられています。

fightersクラスはSQLModelを継承しており、PydanticのValidationが機能し、[id]と[age]にはオプションでint型を、nameとsecret_nameには強制的にstr型が入ります。


##DBにデータを格納する

上記で書いたテーブルとデータをDBへ格納します。

```python
from typing import Optional
from sqlmodel import Field, SQLModel, create_engine, Session


class fighters(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None

#1.fightersのインスタンスを作成します。
fighter_1 = fighters(name="朝倉未来", secret_name="路上の伝説", age=29)
fighter_2 = fighters(name="朝倉海", secret_name="海ぴょん")
fighter_3 = fighters(name="斎藤光", secret_name="シバター", age=35)

#2.DB接続情報を作る。localhostやIPが入ります。3306は今回使用したMySQLのポートです。
url = f'mysql+://{"user名"}:{"DBのpassword"}@{"ソース"}/{"テーブル名"}'
engine = create_engine(url, echo=True)

#3.データベースを作成、セッションをはりデータをコミットします。
SQLModel.metadata.create_all(engine)

with Session(engine) as session:
    session.add(fighter_1)
    session.add(fighter_2)
    session.add(fighter_3)
    session.commit()
```

以下のコマンドを入力
```python3 main.py
```
がしかし
```ModuleNotFoundError: No module named 'MySQLdb'
```

sqlalchemy内部でMySQLdbというモジュールをインポートしているがそれが見当たらないとのこと。

pymysqlのインストールとソースの書き換えをする。
```pipenv install pymysql```
```url = "mysql+pymysql://{"user名"}:{"DBのpassword"}@{"ソース"}/{"テーブル名"}'
```

で、再度
```python3 main.py
```

```
2021-11-04 00:31:26,438 INFO sqlalchemy.engine.Engine INSERT INTO fighters (name, secret_name, age) VALUES (%(name)s, %(secret_name)s, %(age)s)
2021-11-04 00:31:26,438 INFO sqlalchemy.engine.Engine [generated in 0.00077s] {'name': '朝倉未来', 'secret_name': '路上の伝説', 'age': 29}
2021-11-04 00:31:26,458 INFO sqlalchemy.engine.Engine INSERT INTO fighters (name, secret_name, age) VALUES (%(name)s, %(secret_name)s, %(age)s)
2021-11-04 00:31:26,458 INFO sqlalchemy.engine.Engine [cached since 0.02074s ago] {'name': '朝倉海', 'secret_name': '海ぴょん', 'age': None}
2021-11-04 00:31:26,493 INFO sqlalchemy.engine.Engine INSERT INTO fighters (name, secret_name, age) VALUES (%(name)s, %(secret_name)s, %(age)s)
2021-11-04 00:31:26,493 INFO sqlalchemy.engine.Engine [cached since 0.05552s ago] {'name': '斎藤光', 'secret_name': 'シバター', 'age': 35}
2021-11-04 00:31:26,523 INFO sqlalchemy.engine.Engine COMMIT
```

通った。

MySQLworkbenchで中を覗いてみます。
![Screenshot from 2021-11-04 00-47-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2245739/15491219-4334-06f0-da4b-7607d733a180.png)



無事接続＆テーブル作成できました。

###所感
・結局APIのスキーマを定義するときにPydantic使うのなら、それほどメリットはないかも。
・SQLModelはパッケージ管理にPoetry使ってるらしい。pipenvじゃなくてPoetryにしようかな。
・Mysqlとの接続方法はこれでよいのか？
・
今回FastAPI関係なかったな


次回はCRUDをやります。
間違いがあれば指摘していただけると助かります。