Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
11
Help us understand the problem. What is going on with this article?
@penpenta

Pythonのmigrationツールのalembicを動かしてみた

目標

python の migration パッケージalembic · PyPIを動かしてみる

alembic とは??

Python で SQLAlchemy を使用しているときに DB の管理をしてくれる migration ツール

環境準備

Docker で環境を構築

手軽に構築したいので Docker で行う

フォルダ構成
.
├── README.md
├── docker-compose.yml
└── src
    └── model.py
docker-compose.yml
version: "3"

services:
  db:
    image: postgres:11.7
    container_name: alembic-db
    ports:
      - 5432:5432
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=almebic
  app:
    image: python:3.8.2-slim
    container_name: alembic-app
    volumes:
      - ./src:/usr/src
    environment:
      - PYTHONPATH=${PYTHONPATH}:/usr/src
    tty: true

alembic のインストール

pip で必要なパッケージをインストールする

pip install alembic psycopg2-binary

alembic をインストールすれば SQLAlchemy も同時にインストールされる
psycopg2-binary · PyPI は postgres に接続するために使用する

alembicのインストール
root@9a7582105665:/usr/src# pip install alembic psycopg2-binary
Collecting alembic
  Downloading alembic-1.4.2.tar.gz (1.1 MB)
     |████████████████████████████████| 1.1 MB 7.8 MB/s
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
    Preparing wheel metadata ... done
Collecting psycopg2-binary
  Downloading psycopg2_binary-2.8.5-cp38-cp38-manylinux1_x86_64.whl (3.0 MB)
     |████████████████████████████████| 3.0 MB 32.3 MB/s
Collecting python-dateutil
  Downloading python_dateutil-2.8.1-py2.py3-none-any.whl (227 kB)
     |████████████████████████████████| 227 kB 23.8 MB/s
Collecting Mako
  Downloading Mako-1.1.2-py2.py3-none-any.whl (75 kB)
     |████████████████████████████████| 75 kB 11.2 MB/s
Collecting SQLAlchemy>=1.1.0
  Downloading SQLAlchemy-1.3.16-cp38-cp38-manylinux2010_x86_64.whl (1.2 MB)
     |████████████████████████████████| 1.2 MB 54.3 MB/s
Collecting python-editor>=0.3
  Downloading python_editor-1.0.4-py3-none-any.whl (4.9 kB)
Collecting six>=1.5
  Downloading six-1.14.0-py2.py3-none-any.whl (10 kB)
Collecting MarkupSafe>=0.9.2
  Downloading MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl (32 kB)
Building wheels for collected packages: alembic
  Building wheel for alembic (PEP 517) ... done
  Created wheel for alembic: filename=alembic-1.4.2-py2.py3-none-any.whl size=159543 sha256=dc29f47f6c24908d9413da7e3c969c64c252d0cbf9f90fca7cfbb5782b2452d0
  Stored in directory: /root/.cache/pip/wheels/70/08/70/cea787a7e95817b831469fa42af076046e55a05f7c94657463
Successfully built alembic
Installing collected packages: six, python-dateutil, MarkupSafe, Mako, SQLAlchemy, python-editor, alembic, psycopg2-binary
Successfully installed Mako-1.1.2 MarkupSafe-1.1.1 SQLAlchemy-1.3.16 alembic-1.4.2 psycopg2-binary-2.8.5 python-dateutil-2.8.1 python-editor-1.0.4 six-1.14.0
パッケージのversionを確認
root@ecce2b20848e:/usr/src# pip list
Package         Version
--------------- -------
alembic         1.4.2
Mako            1.1.2
MarkupSafe      1.1.1
pip             20.1
psycopg2-binary 2.8.5
python-dateutil 2.8.1
python-editor   1.0.4
setuptools      46.1.3
six             1.14.0
SQLAlchemy      1.3.16
wheel           0.34.2

alembic の環境を作成

alembic init で migration の環境の作成

alembic init {migrationの環境名}

で migration の環境を作成する

alembicの初期設定
root@ecce2b20848e:/usr/src# alembic init migration
  Creating directory /usr/src/migration ...  done
  Creating directory /usr/src/migration/versions ...  done
  Generating /usr/src/migration/README ...  done
  Generating /usr/src/alembic.ini ...  done
  Generating /usr/src/migration/env.py ...  done
  Generating /usr/src/migration/script.py.mako ...  done
  Please edit configuration/connection/logging settings in '/usr/src/alembic.ini' before proceeding.

作成が完了すると以下のような構造になる
migration ディレクトリalembic.ini ファイル ができたことが確認できる

tree
.
├── README.md
├── docker-compose.yml
└── src
    ├── alembic.ini
    ├── migration
    │   ├── README
    │   ├── env.py
    │   ├── script.py.mako
    │   └── versions
    └── model.py

alembic init で生成されるファイルについて

  • env.py
    alembic のツールが起動する度に実行される Python のスクリプトが書かれている
    SQLAlchemy の Engine を設定・生成し migration が実行できるように書かれている
    カスタマイズ可能なスクリプト

  • README.md
    どのような環境で migration 環境を作成したか記述してある

  • script.py.mako
    新しい migration のスクリプトを生成するために使用される Mako テンプレートファイル
    ここにあるものは何でも version/内の新しいファイルを生成するために使用される

  • versions ディレクトリ
    migration スクリプトが保存されるディレクトリ

  • alembic.ini
    alembic のスクリプトが実行された時に探すファイル
    実行時の設定を記述する
    ex. env.py の場所・log の出力・migration ファイルの命名規則など

migration の実行

alembic.ini の編集

DB に接続できるように alembic.ini ファイルを編集する
ini ファイルの以下の箇所を DB の接続情報に書き換える

alembic.ini(編集前)
sqlalchemy.url = driver://user:pass@localhost/dbname
alembic.ini(編集後)
sqlalchemy.url = postgresql://postgres:postgres@alembic-db:5432/almebic

接続情報は docker-compose.yml に記述してあるものを使用する

migration ファイルの作成

revision コマンドで migration ファイルを作成する

alembic revision -m {ファイル名}
migrationファイルの作成
root@ecce2b20848e:/usr/src# alembic revision -m "create account table"
  Generating /usr/src/migration/versions/b5f586d58141_create_account_table.py ...  done

実行後に versions ディレクトリ配下に migration ファイルが作成される

b5f586d58141_create_account_table.py
"""create account table

Revision ID: b5f586d58141
Revises:
Create Date: 2020-05-02 17:49:20.493493

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'b5f586d58141'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    pass


def downgrade():
    pass

migration ファイルの編集

生成された migration ファイルを編集する
ここでは公式のをコピーして account テーブルを作成する

b5f586d58141_create_account_table.py
"""create account table

Revision ID: b5f586d58141
Revises:
Create Date: 2020-05-02 17:49:20.493493

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'b5f586d58141'
down_revision = None
branch_labels = None
depends_on = None


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')

migration の実行

upgrade コマンドで migration を実行する

alembic upgrade head

head は最新の version まで migration を実行する
1 つだけ version を上げたい場合は、head ではなく +1 を使用する
version を下げたいときは downgrade コマンドを使用する
初期状態まで戻したい場合は

alembic downgrade base

を実行する
一つ前の version に戻すときは、base ではなく -1 を使用する

migrationの実行
root@ecce2b20848e:/usr/src# alembic upgrade head
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> b5f586d58141, create account table

migration のファイルを自動で生成させる

Auto Generating Migrations — Alembic 1.4.2 documentationにあるように env.py を編集することで
Python で定義している SQLAlchemy のモデルの情報から自動的に migration ファイルを作成できるようになる
まずは SQLAlchemy でモデルの定義を行う

SQLAlchemy のモデルの定義

モデルの定義では先ほど追加した account テーブルを SQLAlchemy のモデルで定義して
created_atupdated_at の列を追加する
env.py に SQLAlchemy の Engine を渡したいので定義する

model.py
from datetime import datetime

from sqlalchemy import create_engine, Column, String, Integer, Unicode, DateTime
from sqlalchemy.ext.declarative import declarative_base

# Engine の作成
Engine = create_engine(
    "postgresql://postgres:postgres@alembic-db:5432/almebic",
    encoding="utf-8",
    echo=False
)

'''
モデルの Base を作成
この Base を基にモデルを定義するとmetadataにモデルの情報が格納されていく
'''
ModelBase = declarative_base()


class AcountModel(ModelBase):
    """
    AcountModel
    """
    __tablename__ = 'account'

    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)
    description = Column(Unicode(200))
    created_at = Column(DateTime, default=datetime.now, nullable=False)
    updated_at = Column(DateTime, default=datetime.now, nullable=False)

env.py の編集

env.py を編集して model.py で定義したモデルの情報を取得できるようにする
先頭で先ほど定義した ModelBaseEngine を import する
target_metadataModelBase.metadata を代入する
また、migration の実行時には run_migrations_online() を編集して migration が実行されるようにする

env.py
from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context
from model import ModelBase, Engine

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = ModelBase.metadata

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()


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.

    """
    url = config.get_main_option("sqlalchemy.url")
    connectable = Engine

    with connectable.connect() as connection:
        context.configure(
            url=url,
            connection=connection,
            target_metadata=target_metadata
        )

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

autogenerate を使用した migration の実行

revision コマンドに --autogenerate というオプションを付けると SQLAlchemy で定義したモデルの情報から migration ファイルを作成する

revisionコマンド(--autogenerate有り)
root@9a7582105665:/usr/src# alembic revision --autogenerate -m "Added columns."
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.ddl.postgresql] Detected sequence named 'account_id_seq' as owned by integer column 'account(id)', assuming SERIAL and omitting
INFO  [alembic.autogenerate.compare] Detected added column 'account.created_at'
INFO  [alembic.autogenerate.compare] Detected added column 'account.updated_at'
  Generating /usr/src/migration/versions/dcd0d354f648_added_columns.py ...  done

実行後に以下のようなファイルが作成される

dcd0d354f648_added_columns.py
"""Added columns.

Revision ID: dcd0d354f648
Revises: b5f586d58141
Create Date: 2020-05-02 18:58:03.864154

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'dcd0d354f648'
down_revision = 'b5f586d58141'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('account', sa.Column('created_at', sa.DateTime(), nullable=False))
    op.add_column('account', sa.Column('updated_at', sa.DateTime(), nullable=False))
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('account', 'updated_at')
    op.drop_column('account', 'created_at')
    # ### end Alembic commands ###

あとは upgrade コマンドで migration が完了する

おまけ

migration で生成されるファイルの名前を変えたい時は?

dcd0d354f648_added_columns.pyではなく命名規則を変更したい時(日付の情報を含ませたいなど)は alembic.ini を編集する
alembic.ini の file_template を編集する

file_template = %%(year)d%%(month).2d%%(day).2d-%%(hour).2d%%(minute).2d_%%(slug)s

autogenerate はどんなな変更でも検知してくれるの?

一部検知してくれない変更もあるので注意が必要
詳しくは下記を参照
Auto Generating Migrations — Alembic 1.4.2 documentation

11
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
penpenta
エンジニア4年生 疑問に思ったことや調べてみたこと書いてきます。。。 お手柔らかにお願いします。 質問や指摘は大歓迎です。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
11
Help us understand the problem. What is going on with this article?