LoginSignup
66
60

More than 5 years have passed since last update.

開発の途中ですがAlembicを導入します

Last updated at Posted at 2014-02-21

DBスキーマのバージョン管理したい

ちゃんとしよう、と思ったのでAlembicを入れた。

DBスキーマのバージョン管理ィ~?
http://www.slideshare.net/kwatch/db-28097225

インストールや基本的な使い方はここが最高に参考になりましたのでもうこれでいいと思う
http://momijiame.tumblr.com/post/45191790683/python-alembic-rdb

開発途中でも簡単に導入できる

ところで、今回のぼくのケースは、もうアプリ作り始めちゃってるから、既にSQLAlchemyのモデル定義がいくつかあって、もちろんDBにもテーブルが既にあって、何、これAlembicのマイグレーションスクリプトにもう一回テーブルの定義を全部書いてop.create_table しないといけないの? マジで? つらい…… 戦争くらいつらい…… でも戦争のほうがつらいよな……

と思っんだけど、ちゃんとDBとSQLAlchemyの定義読んできてマイグレーションスクリプトを自動生成する機能がある。

できる子。この子になら財布預けられる。
ということでこの通りにやってみる。

マイグレーションスクリプトの自動生成

Alembicの初期設定は最初のサイトを参考に終わってる想定。
といっても alembic init をしたあと、出来たalembic.ini で、sqlalchemy.url がちゃんと設定できていればDBには繋がるのでOKです多分。

で、本題。
自動生成のネタ元として、自分のアプリのBaseの場所を教えてあげる。

env.py
import webapp.model.database
target_metadata = Base.metadata

これだけでいいみたい。
最初のマイグレーションスクリプトを作ってみる。
--autogenerate をつけると自動でなんとかしてくれるんだって。すごいね。自動だ〜いすき!自動もちつき機とか!

その前に、ここで、DB上でテーブルをすべて一旦削除する。

自動生成は、DBとSQLAlchemyのメタデータとの差分を検知するもよう。
これから自動生成する最初のスクリプトには、テーブル生成のスクリプトにしたいので、DBは空の状態が良い。んじゃないですかね。どうでもいいと思います。

ということで、DBのテーブルを一旦全部削除してみました。
そのあと、早速コマンド打つ。

$ alembic revision --autogenerate -m "Create table"

alebmic/versions/の下になんか勝手にできた! テンションあがる!
出来ないときは、Baseが正しくimportできてないとかだと思う。

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

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('order_logs')
    op.drop_table('order_items')
    op.drop_table('users')
    ### end Alembic commands ###


def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.create_table('users',
    sa.Column('id', sa.INTEGER(), primary_key=True),
    sa.Column('name', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('mail_address', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('password', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('status', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('insert_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
    sa.Column('update_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
    sa.PrimaryKeyConstraint('id', name='users_pkey')
    )
    op.create_table('order_items',
    sa.Column('id', sa.INTEGER(), primary_key=True),
    sa.Column('order_log_id', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('item_name', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('item_price', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('item_number', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('item_total', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('status', sa.SMALLINT(), autoincrement=False, nullable=True),
    sa.Column('insert_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
    sa.Column('update_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
    sa.PrimaryKeyConstraint('id', name='order_items_pkey')
    )
    op.create_table('order_logs',
    sa.Column('id', sa.INTEGER(), primary_key=True),
    sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('order_no', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('order_name', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('order_name_kana', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('order_zip', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('order_address', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('order_mail_address', sa.TEXT(), autoincrement=False, nullable=True),
    sa.Column('order_phone_number', sa.TEXT(), autoincrement=False, nullable=True),
     sa.Column('total', sa.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('status', sa.SMALLINT(), autoincrement=False, nullable=True),
    sa.Column('insert_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
    sa.Column('update_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
    sa.PrimaryKeyConstraint('id', name='order_logs_pkey')
    )

いいね!
で、作ったマイグレーションスクリプトのupgrade()を実行してみる。

$ alembic upgrade head

DBにテーブルが出来た!

試しにdowngradeする。

$ alembic downgrade -1

DBからテーブルが消えた!(1こ前は空だったからです)

$ alembic upgrade +1

また出来た! 従順すぎる! 1回くらい逆らってもいいのに!

これ以降も同じ感じでいける

以降、テーブルの定義を変えるときは、Alembicでマイグレーションスクリプトを作って、それを通してDBをいじくることにします。

SQLAlchemy 側でテーブル定義を書き換えたあとにもういっかい alembic revision --autogenerate すれば、DBの状態と比較して差分のマイグレーションスクリプトが自動で作られる。ウヒョ〜!!

だからまあ、やることは殆ど変わらなくて、

  1. SQLAclhemyのテーブル定義をいじる
  2. DBに反映するときに、SQLAlchemyの drop() create()ではなくて、Alembicのalembic revision --autogenerate -m "hoge"alembic upgrade +1 という手順になるだけ。

やべーわ、まずったわー、というとき直せるし、良いんじゃないかと思いました。

その他使ってて気づいたこと

テーブル定義を間違って発行してもゆるいから平気

alembic revision --autogenerate で新規マイグレーションスクリプト発行したあと、やっべー今の無し、ってときには、生成されたスクリプト消すだけでよさそう。ゆるい。

間違ったまま発行して適用しちゃってもゆるいから平気

DBに適用したあとマイグレーションスクリプトに間違いを見つけちゃったら、 alembic downgrade -1 でDBを1つ前の状態に戻し、しれっとスクリプト直して、 alembic upgrade +1 でよさげ。ゆるい。

自動生成の限界はある

デフォルト値や外部キーをつけたくらいでは、Alembicは差分を認識してくれなかった。これは手でスクリプト書かないといけなくてちょっと残念。
ちなみに
http://alembic.readthedocs.org/en/latest/tutorial.html#auto-generating-migrations
この最後のほうに、差分として見つけられるもの、見つけてやらんこともないもの、見つけられないものの一覧がある。まあ、そんなかんじ。

関数がうまく変換されない気がする

例えばモデルの定義で、デフォルトは現在時刻、

insert_date = Column(DateTime, server_default=func.now())

なんてやったとき、これから作られたスクリプトはこうなった。

sa.Column('insert_date', sa.DateTime(), server_default=func.now(), nullable=True),

これはまずくて、うっかりこれを実行すると、func.now() がその場で評価されて、デフォルト値が、これを実行した日付、例えば 2014/1/1 00:00:00 固定になってしまう。

今、手動でスクリプトを

sa.Column('insert_date', sa.DateTime(), server_default=sa.func.now(), nullable=True),

と書き換えてから実行しているんだけど、これって僕がまずいんですかね…… 誰か教えてください……

おしまい

66
60
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66
60