💽

DBマイグレーションツールのAlembicの使い方

2022/03/21に公開1

Djangoようなフルスタックフレームワークにあるようなマイグレーション機能だけを切り出したツールとして、PythonにはAlembicというマイグレーションツールがあります。

ここではAlembicの基本的な使い方について解説します。また、ORMのSQLAlchemyとの併用の仕方についても解説します。

ここではDBはsqliteを使用します。

インストール

まずはPythonの仮想環境を作成します。ここではvenvを使用します。

$ python -m venv "python環境名"

# ここではvenvという名前で作成
$ python -m venv venv

$ source ./venv/bin/activate

alembicをインストールする。

$ pip install alembic

環境のセットアップ

次にalembicの環境を作成していきましょう。以下のinitで、alembicのプロジェクトとが作成されます。

$ alembic init "マイグレーション環境名"

# migrationsという名前で作成
$ alembic init migrations

migrationsというディレクトリが作成され、中身を見ると以下のようなファイル類が作られているはずです。

$ tree
.
├── alembic.ini
└── migrations
    ├── README
    ├── env.py
    ├── script.py.mako
    └── versions

alembic.ini

alembicのスクリプトが実行される時に読まれる構成ファイル。実行時の設定を記述する。

  • env.py の場所
  • log の出力
  • migration ファイルの命名規則

env.py

マイグレーションツールが実行された時に必ず実行されるPythonスクリプト。

SQLAlchemyのEngineを設定や生成を行って、migrationが実行できるようにカスタマイズする。

script.py.mako

新しいmigrationのスクリプトを生成するために使用される Mako テンプレートファイル。

ここにあるものは何でもversions内の新しいファイルを生成するために使用される。

versions/

migrationスクリプトが保存されるディレクトリ

.ini ファイルを編集する

alembic.iniが別のディレクトリにある場合は、--configオプションで指定することで参照させることができる。

alembic.initにある、sqlalchemy.urlにSQLAlchemy が接続するためのURLを、実行する環境に応じて修正する。

sqlalchemy.url = sqlite:///test.db

SQLAlchemyの設定を行う

データベースの設定を行うsettings.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()

作成すると以下のようなディレクトリ構成になります。

$ tree
.
├── alembic.ini
├── migrations
│   ├── README
│   ├── env.py
│   ├── script.py.mako
│   └── versions
└── settings.py

SQLAlchemyのモデルを作成

次にマイグレーションファイルで自動で生成してもらうための、モデルを作成していきます。

models.pyを作成して、以下の内容を記述します。

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
        )

ここではユーザー情報であるusersというテーブルを作成するためのモデルを作成します。

alembicの環境設定ファイルであるenv.pyを修正する

env.pytarget_metadataを修正します。
settings.pyのBaseをインポートして、Base.metadaを設定します。

env.py
from settings import Base

target_metadata = Base.metadata

もし複数のモデルクラスを参照する必要があれば、次のように設定します。

env.py
from models import User
from models import User2

target_metadata = [
    User.metadata, User2.metadata
]

env.pyにあるrun_migrations_onlineを以下のように修正します。追記したのは、url = config.get_main_option("sqlalchemy.url")と、context.configureにurlを追加します。

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()

マイグレーションファイルを自動生成する

以下のコマンドでマイグレーションファイルを自動生成する。

--autogenerateというオプションを付けることで、SQLAlchemyのモデルの内容を元にマイグレーションファイルを自動生成する。

$ 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にマイグレーションファイルの内容を反映する

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

DBの中身を見てみると以下の2つのテーブルが作られているはずです。

  • alembic_version
  • users

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

Discussion

nyosunyosu

こちら参考にさせていただきました(ありがとうございます)
一点、詰まったところがあったので残しておきます。

env.pyの設定のところですが、

  • env.pyにmodels.pyのモデルクラスをimportしないとautogenerateされないようでした。
  • また複数モデルの場合であっても、Baseクラスを継承していれば、Base.metadataで自動生成されました(ただし各モデルクラスをenv.pyにインポートしておく必要があります)