alembicで環境別にマイグレーションファイルを分割する
alembic
というマイグレーション管理ライブラリを利用したマイグレーションファイルの分割方法について。
実現したいのは、開発環境のrevision
と本番環境用のrevision
を分けたいということです。
分けることで、リリースタイミングですべてのテストが完了したrevision
のみ作成されて、マイグレーションファイルをきれいに管理できるようにするためです。
また、本番リリースのタイミングで開発環境上のマイグレーションを破棄して、本番用のバージョンに置き換えるということもできます。
最終的なディレクトリ構成は以下のようになります。
. ├── alembic.dev.ini ├── alembic.prod.ini ├── app │ ├── __init__.py │ └── models │ ├── __init__.py │ └── users.py ├── app.py ├── db │ ├── __init__.py │ ├── migrate_dev │ │ ├── README │ │ ├── env.py │ │ ├── script.py.mako │ │ └── versions │ │ ├── 2020_02_23_123853_.py │ │ ├── 2020_02_23_125416_.py │ │ └── __init__.py │ └── migrate_prod │ ├── README │ ├── env.py │ ├── script.py.mako │ └── versions │ ├── 2020_02_23_125959_.py │ └── __init__.py └── static
準備
依存ライブラリ
SQLAlchemy==1.3.13 alembic==1.4.0 mysqlclient==1.4.6
pythonのバージョン
Python 3.8.0
環境毎に初期化を行う
※ 一つ一つメモを取りつつ行ったわけではないため、コマンド実行時の結果に差がでる可能性があります。ご参考程度によろしくおねがいします。
init
コマンド使用して初期化します。
alembic init db/migrate_dev
このとき、カレントディレクトリ内にalembic.ini
というファイルが作成されます。
このファイルをalembic.dev.ini
にリネームします。
mv alembic.ini alembic.dev.ini
alembic init db/migrate_prod
こちらも同様にalembic.prod.ini
にリネームしてください。
mv alembic.ini alembic.prod.ini
環境別のenvを修正する
dev
、prod
のenv.py
は同じことを書いてください。
import importlib import os import sys from logging.config import fileConfig from sqlalchemy import engine_from_config, MetaData from sqlalchemy import pool from alembic import context config = context.config fileConfig(config.config_file_name) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MODELS_ROOT = BASE_DIR + '/../app/models' sys.path.append(MODELS_ROOT) target_models = [ "users", ] class BaseEnv: @staticmethod def make_target_metadata(): lst = list(map(lambda x: importlib.import_module(x).Base.metadata, target_models)) m = MetaData() for metadata in lst: for t in metadata.tables.values(): t.tometadata(m) return m 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. """ alembic_config = config.get_section(config.config_ini_section) connectable = engine_from_config( alembic_config, prefix="sqlalchemy.", poolclass=pool.NullPool, ) target_metadata = BaseEnv.make_target_metadata() with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata, compare_type=True, ) with context.begin_transaction(): context.run_migrations() run_migrations_online()
今回はrevision --autogenerate
で自動的にモデルを読み込むためにBaseEnv.make_target_metadata()
を作成しました。
残念ながらenv.py
が実行されるタイミングがalembic revision
が実行されたタイミングなのでapp
以下のmodels
を読み込むのが難しいです。
そのために、以下のようにモデルまでのパスを登録しています。
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MODELS_ROOT = BASE_DIR + '/../app/models'
sys.path.append(MODELS_ROOT)
これを行うことで、以下のようにモデルを定義できます。
target_models = [
"users",
]
最後にメタデータを取得するための関数を通して、target_metadata
を作成しています。
usersテーブル用のモデルを作成する
よくある書き方なので詳細については省略します。
from sqlalchemy import DATETIME, Column, Integer, String, BigInteger from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = "users" id = Column(BigInteger, primary_key=True, nullable=False) name = Column(String(255), nullable=False) email = Column(String(255), nullable=False) created_at = Column(DATETIME, nullable=False) updated_at = Column(DATETIME, nullable=False)
.iniファイルを修正する
init
で作成されたファイルのうち、[alembic]
を以下のように修正します。
[alembic] script_location = db/migrate_dev file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s sqlalchemy.url = mysql://root:admin@localhost:3306/sample_a
※ sqlalchemyとmysqlclientを利用しているため、sqlalchemy.url
は適宜読み替えてください。
同様にalembic.prod.ini
も修正します。
[alembic] script_location = db/migrate_prod file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s sqlalchemy.url = mysql://root:admin@localhost:3306/sample_b
今回はわかりやすく別スキーマを用意して、マイグレーション対象のDBを分けました。
これで、alembic.dev.ini
の場合はsample_a
DBに対してマイグレーションが実行され、alembic.prod.ini
の場合はsample_b
DBに対して実行されるようになります。
revisionを作成する
revisionを作成する際に以下のことが必要です。
- .iniを指定する。
- modelsから自動的に読み込む。
これを以下のコマンドで実現します。
alembic -c ./alembic.prod.ini revision --autogenerate
実行後にdb/migrate_prod/versions
に2020_02_23_123612_.py
というファイルが作成されます。
その中が以下のようになっていれば成功です。
def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('users', sa.Column('id', sa.BigInteger(), nullable=False), sa.Column('name', sa.String(length=255), nullable=False), sa.Column('email', sa.String(length=255), nullable=False), sa.Column('created_at', sa.DATETIME(), nullable=False), sa.Column('updated_at', sa.DATETIME(), nullable=False), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_table('users') # ### end Alembic commands ###
同様にdev
も実行してみてください。
alembic -c ./alembic.dev.ini revision --autogenerate
DBをマイグレーションする
作成された2020_02_23_123612_.py
をDBに反映させます。
alembic -c alembic.dev.ini upgrade head
これを実行すると、sample_a
スキーマにusers
テーブルが生成され、alembic_version
テーブルにRevision ID
が追加されます。
devで作業して、成果物をprodに反映する
今回はUser
モデルにdeleted
を追加します。
deleted = Column(Boolean, default=False)
修正分のリビジョンを作成します。
alembic -c ./alembic.dev.ini revision --autogenerate
2020_02_23_125416_.py
が作成されて、以下のスクリプトが生成されました。
def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.add_column('users', sa.Column('deleted', sa.Boolean(), nullable=True)) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_column('users', 'deleted') # ### end Alembic commands ###
マイグレーションします。
alembic -c alembic.dev.ini upgrade head
これでdeleted
カラムが追加されます。
この段階でdb/migrate_dev/versions
の中身は以下のようになっています。
db/migrate_dev ├── README ├── env.py ├── script.py.mako └── versions ├── 2020_02_23_123853_.py ├── 2020_02_23_125416_.py └── __init__.py
この段階でdb/migrate_prod/versions
の中身は以下のようになっています。
(migrate_prod
はversions
に複数のファイルが含まれておらず、きれいなままです。)
db/migrate_prod ├── README ├── env.py ├── script.py.mako └── versions └── __init__.py
users
テーブルの成果物を本番環境に反映します。
alembic -c ./alembic.prod.ini revision --autogenerate
以下のようにスクリプトが生成されます。
def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('users', sa.Column('id', sa.BigInteger(), nullable=False), sa.Column('name', sa.String(length=255), nullable=False), sa.Column('email', sa.String(length=255), nullable=False), sa.Column('deleted', sa.Boolean(), nullable=True), sa.Column('created_at', sa.DATETIME(), nullable=False), sa.Column('updated_at', sa.DATETIME(), nullable=False), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_table('users') # ### end Alembic commands ###
あとはマイグレーションするだけです。
alembic -c alembic.prod.ini upgrade head
これでsample_b
スキーマにusers
テーブルが生成されました。