alembicとSQLAlchemyでマイグレーションを管理する
alembicをインストールする
$ pip install alembic
$ alembic init migrations
$ tree
.
├── alembic.ini
└── migrations
├── README
├── env.py
├── script.py.mako
└── versions
env.py
alembic のツールが起動する度に読み込まれる Python モジュール。
SQLAlchemy
のEngineを設定や生成を行って、migration
が実行できるようにカスタマイズする。
README.md
どのような環境でmigration環境
を作成したか記述されている。
script.py.mako
新しいmigration
のスクリプトを生成するために使用される Mako テンプレートファイル
ここにあるものは何でもversions
内の新しいファイルを生成するために使用される。
versions
migration
スクリプトが保存されるディレクトリ
alembic.ini
alembic
のスクリプトが実行される度に読まれる構成ファイル。実行時の設定を記述する。
- env.py の場所
- log の出力
- migration ファイルの命名規則
など
alembic.iniの修正
alembic.init
にある、sqlalchemy.url
にSQLAlchemy が接続するためのURLを、実行する環境に応じて修正する。
sqlalchemy.url = sqlite:///test.db
SQLAlchemy.url
を環境変数などから取得する必要がある場合、マイグレーションで複数のデータベースのURLを使用する場合は、env.py
ファイルで設定するようにする。
データベースの設定を行うsettings.py
に以下を記述する。
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
path = 'sqlite:///db.sqlite3'
# path = 'mysql+pymysql://root:@127.0.0.1:3306/alembic_sample'
# Engine の作成
Engine = create_engine(
path,
encoding="utf-8",
echo=False
)
Base = declarative_base()
models.py
にモデルの内容を記述する。
from sqlalchemy import Column, Integer, String
from settings import Base
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
nickname = Column(String)
def __repr__(self):
return "<User('name={}', fullname={}, nickname={})>".format(
self.name,
self.fullname,
self.nickname
)
alembic の環境設定ファイル(env.py
) のtarget_metadata
を修正します。
アプリケーションのモデルクラスの Base.metada
を設定します。
from settings import Base
target_metadata = Base.metadata
もし複数のモデルクラスを参照する必要があれば、次のように設定します。
from models import User
from models import User2
target_metadata = [User.metadata, User2.metadata]
env.pyに更に以下を記述する
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
# 追記
url = config.get_main_option("sqlalchemy.url")
with connectable.connect() as connection:
context.configure(
# 追記
url=url,
connection=connection,
target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
alembic revision ––autogenerateコマンドを実行
$ alembic revision --autogenerate -m "create tables"
versions/
配下に下記のmigrationスクリプトファイル
が出来上がる。
"""create tables
Revision ID: 11e6228374a5
Revises:
Create Date: 2022-03-18 10:37:43.272548
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '11e6228374a5'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('fullname', sa.String(), nullable=True),
sa.Column('nickname', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('users')
# ### end Alembic commands ###
DBのテーブルにSQLAlchemyのモデルを反映
alembic upgrade head
を実行することで、テーブルにモデルを反映させることができる。
$ alembic upgrade head
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 11e6228374a5, create tables
テーブルにはalembic_version
とusers
の2つが生成されているはずです。
alembic_version
の中身を見ると、マイグレーション情報が入っています。
head
は最新のversion
までmigration
を実行する。1 つだけ version を上げたい場合は、head
ではなく+1
を使用する。
version を下げたいときは downgrade コマンドを使用する。
現在の状態を調べる。
$ alembic current
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
11e6228374a5 (head)
履歴を見る(history)
$ alembic history
更新の巻き戻し(downgrade)
##
# None -> 8d4b3cb61f6 -> 3f1cb7f09b7a という順序で更新が行われている。
# 現在の状態は3f1cb7f09b7a
##
# 一つ前の状態へ戻す
$ alembic downgrade 8d4b3cb61f6
INFO [alembic.context] Context impl SQLiteImpl.
INFO [alembic.context] Will assume transactional DDL.
# 現在の状態。8d4b3cb61f6
$ alembic current
INFO [alembic.context] Context impl SQLiteImpl.
INFO [alembic.context] Will assume transactional DDL.
Current revision for sqlite:////home/podhmo/.virtualenvs/pyramid/alembic_sample/data.db: None -> 8d4b3cb61f6, create
# baseを指定すると最初に戻す
$ alembic downgrade base
INFO [alembic.context] Context impl SQLiteImpl.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running downgrade 8d4b3cb61f6 -> None
# 現在の状態。None(最初)
$ alembic current
INFO [alembic.context] Context impl SQLiteImpl.
INFO [alembic.context] Will assume transactional DDL.
Current revision for sqlite:////home/podhmo/.virtualenvs/pyramid/alembic_sample/data.db: None
# 元の状態に戻す 3f1cb7f09b7a
$ alembic upgrade 3f1cb7f09b7a
INFO [alembic.context] Context impl SQLiteImpl.
INFO [alembic.context] Will assume transactional DDL.
INFO [alembic.context] Running upgrade None -> 8d4b3cb61f6
INFO [alembic.context] Running upgrade 8d4b3cb61f6 -> 3f1cb7f09b7a
SQLAlchemyと非同期処理
通常のSQLAlchemyの使用方法では、非同期処理に対応しておらず、FastAPIのasync/awaitによるイベントループを活用した高速なDB処理を行うことができません。ORMは遅延読み込みを多用するため非同期処理に対応するのが難しく、元来SQLAlchemyの低レイヤ実装であるSQLAlchemy Coreによる、ORMよりもprimitiveな書き方(クエリを直接書き下すのに近い記法)でしか非同期処理に対応していませんでした。
SQLAlchemyのバージョン1.4からは、2.0 Styleという新しい書き方によりORMとしてクラスを定義した場合でも、非同期処理がサポートされています。本書ではこちらの書き方に基づき、高速なDBアクセスを可能にします。
alembicのデメリット
- テーブル名の変更(drop/addになりデータが初期化されます)
- カラム名の変更(drop/addになりデータが初期化されます)
- 名前の付いていないユニーク制約 (変更を検知するにはユニーク制約に名前をつけることは必須(e.g. - UniqueConstraint('col1', 'col2', name="my_name"))
- EnumなどのSQLAlchemy特有のカラムの変更
- Columnの順番の指定
SQLAlchemyのTimezoneを含んだDatetime型の定義