IT/언어

[alembic/python] Python의 Migration 툴인 alembic의 사용법

개발자 두더지 2022. 11. 11. 22:18
728x90

일본의 한 블로그 글을 번역한 포스트입니다. 오역 및 직역, 의역이 있을 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.

 

 이번 포스트에서는 python의 migration 패키지인 alembic의 간단한 사용법에 대해서 설명하고자 한다. 

 

 

Alembic이란?


 공식 사이트에도 설명되어 있지만, Python으로 SQLAlchemy를 사용하고 있을 때 DB의 관리해주는 migration 툴이다.

 

 

개발 환경 


 간단하게 구축하고 싶으므로 Docker을 사용할 생각이다.

 

1. 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

 

2. alembic의 설치

 pip으로 필요한 패키지를 설치하자. 참고로 alembic을 설치하면 SQLAIchemy도 동시에 설치된다. psycopg2-binary는 postgres에 접속하기 위해 사용하는 패키지이다.

pip install alembic psycopg2-binary

 

 

alembic의 환경 생성


1. alembic init으로 migration의 환경 생성

alembic init {migration의 환경명}

위 커맨드를 실행시키면 다음과 같이 생성이 진행된다.

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 파일이 생성된다.

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

 

2. alembic init으로 생성된 파일에 대한 설명

1) env.py

 alembic의 툴이 실행될 때마다 실행되는 Python의 스크립트가 기재되어 있다. SQLAIchemy의 Engine의 설정 혹은 생성하고 migration이 실행되도록 작성되어 있다. 커스터마이즈가 가능하다.

2) READEM.md

 어떤 환경에서 migration 환경을 작성했는지 기재되어 있다.

3) script.py.mako

 새로운 migration의 스크립트를 생성하기 위해 사용되는 Mako 템플릿 파일. 여기에 기재되어 있는 무엇이든 version/내의 새로운 파일을 생성하는 데에 사용된다.

4) versions 디렉토리

 migration 스크립트가 저장될 디렉토리

5) alembic.ini

 alembic 스크립트가 실행될 때에 찾는 파일. 실행시의 설정에 대해서 기재되어 있다. 예를 들어 env.py의 패스, log의 추력,  migration 파일의 명명규약 등을 기재한다.

 

 

migration의 실행


1. alembic.ini의 수정

DB에 접속할 수 있도록 alembic.ini 파일을 수정한다. ini 파일의 아래 부분에 DB에 접속하는 정보로 바꿔서 작성한다. 

# 수정 전
sqlalchemy.url = driver://user:pass@localhost/dbname

# 수정 후 
sqlalchemy.url = postgresql://postgres:postgres@alembic-db:5432/almebic

 접속정보는 docker-compose.yml에 적혀있는 것을 사용한다.

 

2. migration 파일의 생성

revision 커맨드로 migration 파일이 생성된다.

alembic revision -m {메모}

 예를 들면 다음과 같이 커맨드를 사용할 수 있다.

root@ecce2b20848e:/usr/src# alembic revision -m "create account table"
  Generating /usr/src/migration/versions/b5f586d58141_create_account_table.py ...  done

 특별히 문제가 없다면versions 디렉토리 아래에 migration 파일이 생성됐음을 알 수 있다. 하나의 예이지만, 파일은 다음과 같이 기재되어 있다.

"""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

 

3. migration 파일의 수정

생성된 migration 파일을 수정할 수 있다. 여기서는 공식 사이트에서 복사한 account 테이블을 작성한 migration 파일이다.

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

 

4. migraion의 실행

 upgrade 커맨드로 migration을 실행한다.

alembic upgrade head

 head는 최신의 version까지 migration을 실행한다는 의미이다. 1개만 version을 올리고 싶은 경우 head가 아닌 +1을 사용한다. version을 낮추고 싶은 경우는 downgrade커맨드를 사용한다. 예를 들면 다음과 같이 말이다.

alembic downgrade base

 바로 전의 version을 돌아가고 싶은 경우 base가 아닌 -1를 사용한다.

 

 

migration 파일을 자동으로 생성하도록 하기


 공식 사이트에 안내되어 있듯이 env.py를 편집하면 Python으로 정의되어 있는 SQLAIchemy의 모델 정보의 변경을 자동적으로 인식하여 migraion 파일을 생성하도록 할 수 있다.

 먼저 SQLAIchemy로 모델을 정의해두자.

 

1. SQLAIchemy로 모델을 정의

 모델의 정의로 아까 추가한 account 테이블을 SQLAIchemy의 모델로 정의하여 create_at과 updated_at 열을 추가해보자.

그 후에 env.py에  SQLAIchemy의 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)

 

2. env.py의 편집

 env.py를 수정하여 model.py에서 정의한 모델의 정보를 획득하도록 한다. 파일의 맨 앞에 아까 정의한 ModelBase와 Engine을 import한다. 그리고 target_metadataModelBase.metadata를 대입한다. 

 또한, migration의 실행시에는 run_migrations_online() 을 편집하여 migration가 실행되도록 한다.

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

 

3. autogenerate를 사용하여 migration의 실행

 revision 커맨드에 --autogenerate이라는 옵션을 붙이면 SQLAIchemy로 정의한 모델의 정보에서 migration 파일이 생성된다.

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

 실행결과는 다음과 같다.

"""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 ###

 그 후의 반영 방법은 위와 동일하다.


참고자료

https://qiita.com/penpenta/items/c993243c4ceee3840f30

728x90