Help us understand the problem. What is going on with this article?

Alembicを使う際のメモ

More than 1 year has passed since last update.

Alembic は python の orm である sqlalchemy のマイグレーションツールです。
日本語記事が少なく、なんとなく使っていると気づかないことが多かったので、はまっところをメモに残しておきます。

Alembic:http://alembic.zzzcomputing.com/en/latest/

複数のモデルファイルを扱いたい

複数のmodelファイルがある際、Baseクラスのmetadataを結合させる必要があります。
http://liuhongjiang.github.io/hexotech/2015/10/14/alembic-support-multiple-model-files/
このブログにある方法でうまくいきます。
僕の場合、動的インポートを使ってみました。

env.py
import importlib
from sqlalchemy.schema import MetaData

target_models =[
    'path.to.models',
    'another.models'
]

def import_model_bases():
    """import all target models base metadatas."""
    lst = list(map(
        lambda x: importlib.import_module(x).Base.metadata,
        target_models
    ))
    return lst


def combine_metadata(lst):
    m = MetaData()
    for metadata in lst:
        for t in metadata.tables.values():
            t.tometadata(m)
    return m

target_metadata = combine_metadata(import_model_bases())

型の変更を検知しない

同じカラム名のままだと、型の変化を感知してくれません。
デフォルトだとそう設定されているようです。
http://stackoverflow.com/questions/17174636/can-alembic-autogenerate-column-alterations
こちらもここの記事のように修正します。

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.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix='sqlalchemy.',
        poolclass=pool.NullPool)

    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
            compare_type=True # 追加
        )

        with context.begin_transaction():
            context.run_migrations()

BooleanとTinyintを異なる型と判断する

先ほどの型の変更を感知するようになると今度は Boolean 型を扱う際に問題になります。
Boolean は mysql では tinyint で設定されるのですが、比較する際に型が異なると判断され毎回カラム削除と作成を行おうとしてきます。
また tinyint から integer に型を変えてものうまく検知できません。

ここを参考に書かれている感じで直せばうまくいきます。
僕は以下のようにしてみました。

env.py
# 追加
from sqlalchemy import engine_from_config, types
from sqlalchemy.dialects import mysql

def my_compare_type(context, inspected_column,
                    metadata_column, inspected_type, metadata_type):
    """my compser type for mysql."""
    if isinstance(inspected_type, mysql.TINYINT) and\
       isinstance(metadata_type, types.Boolean):
        return False
    if isinstance(inspected_type, mysql.TINYINT) and\
       isinstance(metadata_type, types.Integer):
        return True
    return None

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)

    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
            compare_type=my_compare_type # 変更
        )

        with context.begin_transaction():
            context.run_migrations()

alembic は機能が多く、未だ使ったことない機能が多いので後でハマったことは順次追記していきます。

orange634nty
エンジニアしてるけど、エンジニアみたいなことはしてない。GoとかC#(Unity)とかRustとか気になるお年頃。
http://orange634.hatenablog.com/
mixi
全ての人に心地よいつながりを
http://mixi.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away