Alembicを使う際のメモ

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/
このブログにある方法でうまくいきます。
僕の場合、動的インポートを使ってみました。

```python: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
こちらもここの記事のように修正します。

```python: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 に型を変えてものうまく検知できません。

- https://bitbucket.org/zzzeek/alembic/issues/46/mysqltinyint-display_width-1-vs-saboolean
- http://alembic.zzzcomputing.com/en/latest/autogenerate.html#comparing-types

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

```python: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 は機能が多く、未だ使ったことない機能が多いので後でハマったことは順次追記していきます。