SQLModelをAlembicでmigrationした話し

# はじめに

FAST APIを使った簡易CRM実装の続きを記事にします。
今回は、SQLModelで定義したモデルをAlembicでmigrationした際の話しをまとめました。

過去記事(参考)
1. [FAST APIを使って簡易CRMを作った話し その1](https://qiita.com/katekichi/items/925dcfadd1d926feba3d
)
1. [FAST APIを使って簡易CRMを作った話し その2](https://qiita.com/katekichi/items/40c2f9b17f502bc1cdf6
)
1. [FAST APIを使って簡易CRMを作った話し その3](https://qiita.com/katekichi/items/178eec96cd1896a6a558
)

## おさらい
環境：
・macOS 12.6 Apple M1 Pro
・Python 3.12.7 

models.pyを分割して、modelsフォルダを作成して各モデル毎にファイルを分割しました。

```
models
├── __init__.py
└── customer.py
```

```__init__.py
from app.models.customer import Customer
```

```customer.py
from sqlmodel import SQLModel, Field

class Customer(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    address: str = Field(default=None)
```

## Alembicを使ってmigrationをする

今回参考にした記事はこちらです。

https://zenn.dev/mook_jp/books/sqlmodel-tutorial/viewer/alembic#model%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B

```sh
$ poetry add alembic
$ alembic --version
alembic 1.14.0
```

## 環境の初期化

```sh
$ alembic init migrations
```

## 各ファイルの更新
`migrations`フォルダ内の`alembic.ini`のDB接続情報を記述します。

```alembic.ini
sqlalchemy.url = sqlite:///database.db
```

`migrations`フォルダ内の`env.py`にSQLModelを使用するように記述します。
（#追記、#修正箇所）

```env.py
from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context
from app.models import Customer # 追加
from sqlmodel import SQLModel  # 追加

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
    fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = SQLModel.metadata  # 修正

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.

```


## Modelからmigrationする

以下のコマンドでmigrationファイルを作成します。
`-m`以降に指定した文字列で`migrations/versions`フォルダ配下にファイルが作成されます。

```sh
$ alembic revision --autogenerate -m "customer"
```

DBに適用します。

```sh
$ alembic upgrade head   
```

## Modelを修正する
作成日と更新日を追加したかったので、共通で使用するModelを`base.py`として
切り出すようにしました。

`updated_at`の際に行っている`sa_column_kwargs`を指定することにより、`SQLAlchemy`の`sa_column`対して引数を渡すことができます。ここでは、データ更新時に現在時刻を渡したいので、`onupdate`に対して`datetime.now`で取得した値を設定しています。

```models/base.py
from datetime import datetime
from sqlmodel import SQLModel, Field


class Base(SQLModel):
    created_at: datetime = Field(default_factory=datetime.now, nullable=False)
    updated_at: datetime = Field(
        default_factory=datetime.now, nullable=False,
        sa_column_kwargs={'onupdate': datetime.now})
```

```models/customer.py
from sqlmodel import Field
from backend.app.models.base import Base


class Customer(Base, table=True): # SQLModelからBaseへ変更
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    address: str = Field(default=None)
```

migrationファイルの作成と更新をします。

```
$ alembic revision --autogenerate -m "add_base_model"
$ alembic upgrade head  
```

# まとめ

- Djangoのmigrationと比べてファイルの初期設定は必要でしたが、それほど遜色なく使えそうです。次回はSQLModelについて複数Modelのリレーション等を中心にまとめたいと思います。