Python AlembicでPostgres Migration管理

Python系のPostgreSQL Databaseのmigration管理ツールを検討

public以外のスキーマも用いたい。
postgreSQLのデータ型を直接指定したい。


# ツール候補
- django
- alembic ←今回はこれを利用
- sqlalchemy-migrate
- yoyo-migration

# alembic参考ドキュメント
- [pypi alembic](https://pypi.python.org/pypi/alembic)
- [公式doc](http://alembic.zzzcomputing.com/en/latest/index.html)
- [Python の Alembic を使って RDB のスキーマを管理する](http://momijiame.tumblr.com/post/45191790683/python-%E3%81%AE-alembic-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6-rdb-%E3%81%AE%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E%E3%82%92%E7%AE%A1%E7%90%86%E3%81%99%E3%82%8B)


# 使ってみる
## install

```
$ pip install alembic
```


## 作業ディレクトリ作成
複数のDBを使うときはオプション`-t multidb`をつける

```
$ alembic init <DIRNAME>
$ alembic init <DIRNAME> -t multidb
```

## 設定ファイル編集
alembic.iniが設定ファイル。
デフォルトでは<DIRNAME>ディレクトリと同じ場所においておく。
他の場所におく場合は、以後の作業にオプションで`-c <設定ファイルパス>`と指定

データベースのconfig情報を編集

```
sqlalchemy.url = postgresql://<username>:<password>@<hostname>:<port>/<dbname>
```

alembic.iniを他の場所に移す場合は以下を編集

```
script_location = <DIRPATH>
```

## 新migration用スクリプト作成
```
$ alembic revision -m 'Create Table'
```

`'Create Table'`は作成するスクリプト名
`<DIRNAME>/versions/6535ae93bef1_create_table.py`が作成される
ファイル名の`6535ae93bef1`はrevision ID。その都度自動で作成される。

postgreSQLのデータ型を直接指定したいので、以下をimport。

```<DIRNAME>/versions/6535ae93bef1_create_table.py
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql as pg
```

スクリプトファイルのupgradeとdowngradeファンクションに更新内容を記載

```<DIRNAME>/versions/6535ae93bef1_create_table.py
def upgrade():
    op.create_table(
        'users',
        sa.Column('id', pg.INTEGER, primary_key=True),  # serial

        sa.Column('char_col', pg.CHAR(10),
                  nullable=False,  # default:True
                  unique=True,  # default:False
                  server_default='deftext'  # default:None
                  ),
        sa.Column('varchar_col', pg.VARCHAR(32)),
        sa.Column('text_col', pg.TEXT),

        sa.Column('bool_col', pg.BOOLEAN),

        sa.Column('smallint_col', pg.SMALLINT),
        sa.Column('int_col', pg.INTEGER, server_default='100'),
        sa.Column('double_col', pg.DOUBLE_PRECISION),
        sa.Column('numeric_col', pg.NUMERIC),
        schema='test'  # set schema name
    )

    op.create_table(
        'permissions',
        sa.Column('id', pg.INTEGER, primary_key=True),  # serial
        sa.Column('user_id', pg.INTEGER,
                  sa.ForeignKey(
                      'test.users.id', onupdate='CASCADE', ondelete='RESTRICT'
                  ),  # default ON UPDATE NO ACTION ON DELETE NO ACTION
                  ),
        sa.Column('date_col', pg.DATE, server_default=sa.text('NOW()')),  # default NOW()
        sa.Column('time_col', pg.TIME),  # time without time zone
        sa.Column('timetz_col', pg.TIME(timezone=True)),  # time with time zone
        sa.Column('timestamp_col', pg.TIMESTAMP),  # timestamp without time zone
        sa.Column('timestamptz_col', pg.TIMESTAMP(timezone=True)),  # timestamp with time zone
        sa.Column('interval_col', pg.INTERVAL),

        sa.Column('int4range_col', pg.INT4RANGE),
        sa.Column('numrange_col', pg.NUMRANGE),
        sa.Column('daterange_col', pg.DATERANGE),
        sa.Column('tsrange_col', pg.TSRANGE),
        sa.Column('tstzrange_col', pg.TSTZRANGE),

        sa.Column('json_col', pg.JSON),
        sa.Column('macaddr_col', pg.MACADDR),
        sa.Column('inet_col', pg.INET),
        schema='test'
    )


def downgrade():
    op.drop_table('account', schema='test')
```

testスキーマは残念ながら自動で作成されないなので、事前に作成しておく。

```
CREATE SCHEMA test;
```

## upgrade
最新に更新

```
$ alembic upgrade head
```

一つ戻る

```
$ alembic downgrade -1
```

初期状態に戻る

```
$ alembic downgrade base
```

## 状態確認
現在の状態

```
$ alembic current
```

ヒストリ

```
$ alembic history
```