🐈

[和訳] Alembic チュートリアル

2021/02/28に公開

免責

非公式に和訳しました。必ずしも正しく和訳できているとは限りませんのでご容赦ください。参照したのは Alembic 1.5.5 のドキュメントのチュートリアルです。原文はこちらです。

チュートリアル

Alembicはリレーショナルデータベースのマネジメントスクリプトの生成、管理、実行を提供します。Alembic は SQLAlchemy を使用します。このチュートリアルではこのツールの使い方とその理論を説明します。

まず最初に Alembic は Installation で指定された方法でインストールがされているとします。 Installation で指定されたように、通常 Alembic は対象のプロジェクトと同じモジュール内、同じ Python パスにインストールされます。通常 Python の仮想環境を用い、 alembic の Python スクリプト env.py を実行する alembic コマンドがプロジェクトのモデルを読み込めるようにします。これは必ずしも全ての場合で必要とされるわけではありませんが、ほとんどの場合推奨されます。

このチュートリアルでは alembic コマンドラインユーティリティが実行されるときには、ローカルパスに存在し、プロジェクトの Python モジュールにアクセスできるものとします。

マイグレーション環境

マイグレーション環境のの作成から始目ましょう。これは、特定のアプリケーション固有のものです。このマイグレーション環境は一回だけ作成され、アプリケーションのソースコードと共に管理されます。この環境は Alembic の init コマンドによって作成され、アプリケーションの要件によってカスタマイズすることができます。

生成されたマイグレーション用のスクリプトを含むこの環境の構成は以下のようになります。

yourproject/
  alembic/
    env.py
    README
    script.py.mako
    versoins/
      3512b954651e_add_account.py
      2b1ae634e5cd_add_order_id.py
      3adcc9a56557_rename_username_field.py
  • yourproject

    これはアプリケーションソースコードのルートディレクトリかその中のディレクトリです。

  • alembic

    このディレクトリはアプリケーションのソースコードの中に存在し、マイグレーション環境のホームディレクトリとなります。この命名は alembic に限らず自由に行うことができ、プロジェクトが複数のデータベースを使用する場合には複数のディレクトリを扱うこともあります。

  • env.py

    このファイルは、Alembicのマイグレーションツールが実行された時に必ず実行されるPythonスクリプトになります。少なくとも、このファイルには SQLAlchemyエンジンを生成し設定する手続きが含まれ、SQLAlchemyにトランザクションへのコネクションを確立させ、そのコネクションを用いてマイグレーションエンジンが実行されます。

    env.py スクリプトはマイグレーション実行方法をカスタマイズ可能なものにするために生成される環境の一部です。コネクションの確立方法だけでなく、マイグレーション環境がどのように実行されるべきかを記述することができます。このスクリプトによって、複数のエンジンを実行するために編集することも、マイグレーション環境の実行に受け渡す引数を設定することも、アプリケーション固有のライブラリやモデルを読み込んで利用可能にすることもできます。

    Alembic は複数のユースケースに対応するために env.py の初期テンプレートを提供しています。

  • README

    テンプレートで作成される様々な環境が含まれます。READMEとしての情報を含んだドキュメントであるべきです。

  • script.py.mako

    これは、マイグレーションスクリプトを自動生成する Mako のテンプレートファイルです。ここに記述されたものは versions/ 内の新しいファイルの生成に用いられます。これは各マイグレーションファイルの構成をコントロール可能なものにするために編集することができます。その変更は、どのライブラリを標準でインポートするべきかを指定したり、 upgrade()downgrade() 関数の構成を変更したりすることができます。例えば、 multidb 環境では、 upgrade_engine1()upgrade_engine2() などのように異なる名前の複数の関数が生成されることを可能にします。

  • versions/

    このディレクトリは個々のバージョンのスクリプトを保持します。他のマイグレーションツールを使用しているユーザーはこのディレクトリ内のファイルが昇順の数値ではなく GUID を用いていることに気が付くでしょう。 Alembic では、バージョンに関するスクリプトを順序付けることは、スクリプト自体の中の表現に関係しており、理論的にはバージョンファイルを他のものとの間に挿入することが可能になっています。慎重な手作業によって行われるべきですが、異なるブランチからのマイグレーションをマージすることができます。

環境を作成する

この環境への基本的な理解ができたところで、 alembic init によって作成してみましょう。これは、一般的なテンプレートを用いた環境を構築します。

$ cd /path/to/yourproject
$ source /path/to/yourproject/.venv/bin/activate  # ローカルの仮想環境を想定しています
$ alembic init alembic
Creating directory /path/to/yourproject/alembic...done
Creating directory /path/to/yourproject/alembic/versions...done
Generating /path/to/yourproject/alembic.ini...done
Generating /path/to/yourproject/alembic/env.py...done
Generating /path/to/yourproject/alembic/README...done
Generating /path/to/yourproject/alembic/script.py.mako...done
Please edit configuration/connection/logging settings in
'/path/to/yourproject/alembic.ini' before proceeding.

3つ目のコマンドは initコマンドが alembic という名前のマイグレーション環境のディレクトリを作成することを意味しています。

Alembic は他の環境のテンプレートも含んでいます。これらは list_templates コマンドによって確認できます。

$ alembic list_templates
Available templates:

generic - Generic single-database configuration.
multidb - Rudimentary multi-database configuration.
pylons - Configuration that reads from a Pylons project environment.

Templates are used via the 'init' command, e.g.:

  alembic init --template pylons ./scripts
  • generic

    単一データベース用の一般的な設定

  • multidb

    複数データベースのための簡単な設定

  • pylons

    Pylons プロジェクト環境から読み込まれる設定

.ini ファイルの編集

Alembicは init コマンドによって、カレントディレクトリに alembic.ini を生成します。このファイルは alembic スクリプトが実行される時に参照するファイルです。これは alembic コマンドが実行されるときのカレントディレクトリにある必要があります。別のディレクトリにある場合は --config オプションで指定することで参照させることができます。

generic 設定で生成される alembic.ini ファイルは以下のようになります。

# A generic, single database configuration.

[alembic]
# path to migration scripts
script_location = alembic

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
# (new in 1.5.5)
prepend_sys_path = .

# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =

# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; this defaults
# to alembic/versions.  When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = driver://user:pass@localhost/dbname

# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts.  See the documentation for further
# detail and examples

# format using "black" - use the console_scripts runner,
# against the "black" entrypoint
# hooks=black
# black.type=console_scripts
# black.entrypoint=black
# black.options=-l 79

# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

このファイルは Python の ConfigParser.SafeConfigParser オブジェクトを用いることで読み込むことが可能です。 %(here)s は置換可能な変数で、上で行った Alembic スクリプトへのパスのように、ディレクトリやファイルへの絶対パスを生成するために使用することができます。

このファイルは以下のような特性を持っています。

  • [alembic]

    これは Alembic が設定のために読み込むセクションです。 Alembic 自身は他の箇所を直接読み込むことはありません。この alembic という名前は --name フラグによってカスタマイズすることが可能です(詳しくは複数のAlembic環境を一つの .ini ファイルで実行する方法をご覧ください)。

  • script_location

    これは、 Alembic 環境の場所を示しています。これは通常ファイルシステムの場所を示し、相対パスか絶対パスのどちらかで指定されます。もしこの場所が相対パスであった場合は、これはカレントディレクトリからの相対パスとして読み込まれます。

    これはいかなる場合にも Alembic が必要とする唯一のキーです。 alembic init alembic コマンドによる .ini ファイルの生成はディレクトリ名を自動で alembic と決定します。特殊変数 %(here)s%(here)s/alembic として使用されます。

    自分自身を .egg ファイルへパッケージ化するアプリケーションをサポートするために、値をパッケージリソースとして指定することもでき、その場合は resource_filename() がそのファイルを探すために用いられる。ここではコロンを含む絶対パスではないURIは、ファイル名ではなくリソース名として解釈される。

  • file_template

    これは新しいマイグレーションファイルの生成時に用いられる命名規則である。デフォルトの値が設定されているため、ここではコメントアウトされています。利用可能なトークンは、

    • %%(rev)s

      修正の識別子

    • %%(slug)s

      修正のメッセージの先頭の文字列

    • %%(year)d, %%(month).2d, %%(day).2d, %%(minute).2d, %%(second).2d

      作成された日付。timezone 設定がされていない限り、デフォルトでは datetime.datetime.now() から取得されます。

  • timezone

    任意で設定できるタイムゾーンの名前です。 UTCEST5EDT などを設定できます。これはマイグレーションファイルのコメントやファイル名のタイムスタンプに反映されます。もし timezone を設定した場合、作成される date オブジェクトは datetime.datetime.now() ではなく、

    datetime.datetime.utcnow().replace(
        tzinfo=dateutil.tz.tzutc()
    ).astimezone(
        dateutil.tz.gettz(<timezone>)
    )
    

    で作成されます。

  • truncate_slug_length

    デフォルトは 40 文字で設定されています。これは "slug" フィールドに含まれる文字数の最大値になります。

  • sqlalchemy.url

    これは、SQLAlchemyを通して接続するデータベースのURLです。ここで設定される値は、 env.py ファイルが呼ばれた時のみに使用されます。 例えば "generic" のテンプレートでは、 run_migrations_offline() 関数の中で呼ばれる config.get_main_option("sqlalchemy.url") と、 run_migrations_online() 関数の中で呼ばれる engine_from_config(prefix="sqlalchemy.") はこのキーが参照される場所です。もし SQLAlchemy URL が他のソース(例えば環境変数や公開されているレジストリ)から参照されるべき場合は、データベースのURLを獲得するためにどんなメソッドを用いるべきかを env.py ファイルに記述する必要があります。

  • revision_environment

    このフラグが true であれば、新しい修正のファイルを生成するときや alembic history コマンドを走らせるときに、マイグレーション環境のスクリプトである env.py が無条件で実行されることを示します。

  • souceless

    このフラグが true であれば、リビジョンファイルは各バージョンのディレクトリ内の .pyc.pyo ファイルとして存在します。これらのリビジョンファイルはそれぞれがバージョンとして使用され、ソースレスのバージョン管理が可能になります。デフォルトの false のままにしておくと、 .py ファイルのみがバージョンファイルとして使用されます。

  • version_locations

    リビジョンファイルの場所を示すオプショナルなリストです。この設定によってリビジョンファイルが複数のディレクトリに同時に存在させることが可能になります。これを用いた例についての詳細は、 Working with Multiple Base を参照してください。

  • output_encoding

    Alembic が script.py.mako ファイルを用いて新しいマイグレーションファイルを作成する時に使用するエンコーディングを示します。デフォルトでは utf-8 が設定されています。

  • [loggers], [handlers], [formatters], [logger_*], [handler_*], [formatter_*]

    これらのセクションは全て Python の標準的なログギング設定の一部です。その仕組みは Configuration File Format のドキュメントに示されています。データベース接続の場合と同様に、ここでの設定は env.py (自由に編集が可能です)にある logging.config.fileConfig() の結果として直接使用されます。

SQLAlchemy の URL を設定して、データベースは一つで、 generic の設定で開始する場合にはただ一つ必要な設定は

sqlalchemy.url = postgresql://scott:tiger@localhost/test

です。

マイグレーションスクリプトの作成

環境が整ったら alembic revision を用いて新しいリビジョンファイルを生成できます。

$ alembic revision -m "create account table"
Generating /path/to/yourproject/alembic/versions/1975ea83b712_create_accoun
t_table.py...done

1975ea83b712_create_account_table.py が生成されます。ファイルの中身は

"""create account table

Revision ID: 1975ea83b712
Revises:
Create Date: 2011-11-08 11:40:27.089406

"""

# revision identifiers, used by Alembic.
revision = '1975ea83b712'
down_revision = None
branch_labels = None

from alembic import op
import sqlalchemy as sa

def upgrade():
    pass

def downgrade():
    pass

となります。

このファイルはヘッダー情報、現在の変更とダウングレードした変更の識別子、Alembicのインポートと空の empty()downgrade() 関数を含みます。この upgrade() 関数と downgrade() 関数を編集し、データベースの変更についてを記述する必要があります。通常 upgratde() 関数は必須で、 downgrade() 関数はダウングレード機能が必要な場合にのみ必要とされます。

down_revision の変数によって Alembic がマイグレーションがどのような順になっているかを知ることができます。次の変更を生成したときに、 down_revision 識別子は

# revision identifiers, used by Alembic.
revision = 'ae1027a6acf'
down_revision = '1975ea83b712'

のようになります。

Alembic が /versions ディレクトリに対して実行される度に、ディレクトリ内の全てのファイルを読み込みます。そして down_revision 識別子の情報に基づいてリストを構成します。 down_revisionNone であるファイルが最初のファイルであると認識されます。理論上、もしマイグレーション環境が数千のマイグレーションを含む場合、この処理は立ち上げるのに時間を要する可能性があります。しかし実用上、古いマイグレーションファイルは削除されるべきです。どのようにこの処理を行うべきかについては、 Building an Up to Date Database from Scratch を参照してください。

account テーブルを作成するために関数を実装します。

def upgrade():
    op.create_table(
        'account',
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('name', sa.String(50), nullable=False),
        sa.Column('description', sa.Unicode(200)),
    )

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

create_table()drop_table() は Alembic の関数です。 Alembic は基本的なデータベースのマイグレーションの手続きに関する関数を提供しています。それらの関数のほとんどは現在のテーブルのメタデータに依存せず、データベースに接続する方法を示す大域的なコンテキストを使用しています。マイグレーションを SQL/DDL の操作をファイルに出力することもできます。

Alembic のマイグレーションの関数に関しては、 Operation Reference を参照してください。

最初のマイグレーション実行

それでは最初のマイグレーションを実行します。データベースにはなんの情報もなく、バージョン管理もされていないと仮定します。 alembic upgrade コマンドがアップグレードの手続きを行い、現在のデータベースの状況(この例では None)から与えられた変更を実行します。マイグレーションの識別子 1975ea83b712 を指定することで特定の変更を反映させることもできますが、ほとんどの場合もっとも最新の情報を指定することが簡単です。この場合は head で最新であることを指定することができます。

$ alembic upgrade head
INFO  [alembic.context] Context class PostgresqlContext.
INFO  [alembic.context] Will assume transactional DDL.
INFO  [alembic.context] Running upgrade None -> 1975ea83b712

ここで出力される情報は alembic.ini のログの設定を用いています。標準エラーを含めて alembic のストリームをコンソールにロギングします。

この処理において Alembic は最初に alembic_version テーブルが存在するかどうかを確認し、存在しない場合は作成します。最初に現在のバージョンを確認し、もしあればそのバージョンからリクエストされたマイグレーションまでのバージョンを計算します。この場合は head が指定されており、 1975ea83b712 のバージョンとして解釈されます。次にそれぞれのファイルの upgrade() メソッドが実行されます。

二回目のマイグレーション実行

それではもう一つのマイグレーションを実行してみましょう。再びマイグレーションファイルを作成します。

$ alembic revision -m "Add a column"
Generating /path/to/yourapp/alembic/versions/ae1027a6acf_add_a_column.py...
done

このファイルを編集し、 account テーブルにカラムを追加してみます。

"""Add a column

Revision ID: ae1027a6acf
Revises: 1975ea83b712
Create Date: 2011-11-08 12:37:36.714947

"""

# revision identifiers, used by Alembic.
revision = 'ae1027a6acf'
down_revision = '1975ea83b712'

from alembic import op
import sqlalchemy as sa

def upgrade():
    op.add_column('account', sa.Column('last_transaction_date', sa.DateTime))

def downgrade():
    op.drop_column('account', 'last_transaction_date')

再び head に対して実行します。

$ alembic upgrade head
INFO  [alembic.context] Context class PostgresqlContext.
INFO  [alembic.context] Will assume transactional DDL.
INFO  [alembic.context] Running upgrade 1975ea83b712 -> ae1027a6acf

これで last_transaction_date カラムが追加されました。

マイグレーション識別子を省略して参照させる

マイグレーション識別子はその一部を用いることでも指定できます。ただしこの文字列は一意にバージョンを指定できる長さである必要があります。バージョン番号を指定する箇所ではどこでも省略した識別子を指定することができます。

$ alembic upgrade ae1

ここで使った ae1ae1027a6acf を参照しています。複数のバージョンが参照されている場合には Alembic は処理を中断し、それを出力します。

マイグレーションの相対参照

アップグレードやダウングレードを実行する時に相対的な指定もすることもできます。例えば二つの先のバージョンを指定する場合は数値で +N と指定することでできます。

$ alembic upgrade +2

負の値を指定すればダウングレードに対しても指定できます。

$ alembic downgrade -1

相対的な識別子は特定のバージョンを起点にすることもできます。例えば ae1027a6acf から二つ先のバージョンを指定する場合は

$ alembic upgrade ae10+2

とすることができます。

情報の取得

二つのバージョンを作成しましたが、現在の情報を取得することもできます。

まず現在のマイグレーション情報をみてみます。

$ alembic current
INFO  [alembic.context] Context class PostgresqlContext.
INFO  [alembic.context] Will assume transactional DDL.
Current revision for postgresql://scott:XXXXX@localhost/test: 1975ea83b712 -> ae1027a6acf (head), Add a column

head は現在のデータベースと一致していることを示しています。

alembic history によって履歴を確認することもできます。 --verbose オプションを指定すれば各マイグレーションの全情報を取得できます。このオプションは history だけでなく、 currentheadsbranches コマンドでも使えます。

$ alembic history --verbose

Rev: ae1027a6acf (head)
Parent: 1975ea83b712
Path: /path/to/yourproject/alembic/versions/ae1027a6acf_add_a_column.py

    add a column

    Revision ID: ae1027a6acf
    Revises: 1975ea83b712
    Create Date: 2014-11-20 13:02:54.849677

Rev: 1975ea83b712
Parent: <base>
Path: /path/to/yourproject/alembic/versions/1975ea83b712_add_account_table.py

    create account table

    Revision ID: 1975ea83b712
    Revises:
    Create Date: 2014-11-20 13:02:46.257104

指定の履歴を閲覧する

alembic history-r オプションを使えばマイグレーションのバージョンのスライスを指定できます。 -r の引数は [start]:[end] で指定します。 startend はバージョンの識別子や head, heads, base, current によって現在のバージョンを指定することができます。また、 start は負の数値で end に対する相対参照をすることができ、 end は正の数値で start に対する相対参照をすることができます。

$ alembic history -r1975ea:ae1027

相対的な範囲の指定は

$ alembic history -r-3:current

のように指定します。 head を省略することで 1975 から現在のバージョンまでを取得することができます。

$ alembic history -r1975ea:

ダウングレードを行う

ダウングレードをしたい場合は、 alembic downgrade コマンドを使用します。全てのマイグレーションをダウングレードする場合には base を指定します。

$ alembic downgrade base
INFO  [alembic.context] Context class PostgresqlContext.
INFO  [alembic.context] Will assume transactional DDL.
INFO  [alembic.context] Running downgrade ae1027a6acf -> 1975ea83b712
INFO  [alembic.context] Running downgrade 1975ea83b712 -> None

また最新までアップグレードするには

$ alembic upgrade head
INFO  [alembic.context] Context class PostgresqlContext.
INFO  [alembic.context] Will assume transactional DDL.
INFO  [alembic.context] Running upgrade None -> 1975ea83b712
INFO  [alembic.context] Running upgrade 1975ea83b712 -> ae1027a6acf

とします。

Discussion