alembic でインデックスが 767 バイト以上の対策

MySQL5.6 で alembic を使った場合に、
"Warning: Specified key was too long; max key length is 767 bytes" が出る場合の対策メモ。

$ alembic upgrade head
INFO  [alembic.migration] Context impl MySQLImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.migration] Running upgrade  -> 202b6777d6c, Create init table.
/opt/python3.4/site-packages/pymysql
/cursors.py:134: Warning: Specified key was too long; max key length is 767 byte
s
  result = self._query(query)


原因は以下を参照。
MySQL(InnoDB) で "Index column size too large. The maximum column size is 767 bytes." いわれるときの対策 - かみぽわーる
ERROR 1071 (42000): Specified key was too long; max key length is 1000 bytes - sonots:blog


my.cnf で innodb の設定を変える。
MySQL の設定だけを変えて、alembic を実行してもエラーになる。

INFO  [alembic.migration] Context impl MySQLImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.migration] Running upgrade  -> 202b6777d6c, Create init table.
Traceback (most recent call last):
  File "/opt/virtualenvs/pisces/bin/alembic", line 9, in <module>
....
sqlalchemy.exc.InternalError: (pymysql.err.InternalError) (1709, 'Index column s
ize too large. The maximum column size is 767 bytes.') [SQL: 'CREATE INDEX ix_co
ntacts_email ON contacts (email)']


マイグレーションようのスクリプトにも手を入れてやる必要がある。

"""Create init table.

Revision ID: 202b6777d6c
Revises: None
Create Date: 2014-10-11 18:23:14.081574

"""

# revision identifiers, used by Alembic.
revision = 'xxxxx'
down_revision = None

import os
import simplejson as json
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import table, column
from sqlalchemy.dialects.mysql import INTEGER as Integer


def upgrade():
    op.create_table(
        'users',
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('name', sa.Unicode(50), nullable=False, index=True),
        sa.Column('password', sa.String(128), nullable=False, index=True),
        sa.Column('email', sa.String(254), nullable=False, index=True),
        sa.Column('created_at', sa.DateTime),
        sa.Column('updated_at', sa.DateTime),
        mysql_row_format='DYNAMIC'
    )

def downgrade():
    op.drop_table('users')

こんな感じで `create_table()` で mysql_xxx という形で MySQL 特有のオプションを渡す。
これで実行すると、エラーなく完了する。

 $ alembic upgrade head
INFO  [alembic.migration] Context impl MySQLImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.migration] Running upgrade  -> xxxxx, Create init table.