0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FastAPIとAlembicを使ってマイグレーションファイルを管理しよう!

Posted at

概要

Webアプリケーション作成時にmodelの定義を反映させたり、管理する場合はマイグレーションファイルを使用するのが一般的です
今回はFastAPIととAlembicを使ってマイグレーションファイルを管理する方法について解説します

前提

  • SQLAlchemyのバージョン2以上を使用
  • Alembicをインストール済み
  • DBはPostgresを使用

実装

まず、alembicの初期設定を行います
以下のコマンドで初期化します

alembic init {マイグレーションファイル名}

今回はマイグレーションファイルを管理するフォルダの名前をmigrationsにします

poetry run alembic init migrations
Creating directory '/code/migrations' ...  done
Creating directory '/code/migrations/versions' ...  done
Generating /code/alembic.ini ...  done
Generating /code/migrations/script.py.mako ...  done
Generating /code/migrations/env.py ...  done
Generating /code/migrations/README ...  done
Please edit configuration/connection/logging settings in '/code/alembic.ini' before proceeding.

以下のようにmigrationsフォルダ配下にファイルが作成されたら成功です

└── application
    ├── __init__.py
    ├── migrations
    │   ├── README
    │   ├── env.py
    │   ├── script.py.mako
    │   └── versions
    ├── alembic.ini
    ├── database.py
    └── models.py

database.pyにFastAPIのアプリケーションとDBとの接続設定を記載します

database.py
import os

from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker

SQLALCHEMY_DATABASE_URL = os.environ.get("SQLALCHEMY_DATABASE_URL")

engine = create_engine(SQLALCHEMY_DATABASE_URL)

# DBセッションのオブジェクトを生成
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


# Baseクラスを元にモデルを定義する
# SQLAlchemyのバージョン2以降からdeclarative_baseではなく、DeclarativeBaseクラスを使うことが推奨されている
class Base(DeclarativeBase):
    pass

Modelの定義を行います

models.py
from database import Base
from sqlalchemy import Boolean, Column, Integer, String


class Users(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    email = Column(String, unique=True)
    username = Column(String, unique=True)
    first_name = Column(String)
    last_name = Column(String)
    password = Column(String)
    is_active = Column(Boolean, default=True)
    role = Column(String)
    phone_number = Column(String)


class Todos(Base):
    __tablename__ = "todos"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String)
    description = Column(String)
    priority = Column(Integer)
    complete = Column(Boolean, default=False)

env.pyにSQLALCHEMY_DATABASE_URLが入るよう修正します

env.py
from logging.config import fileConfig

from alembic import context
from database import SQLALCHEMY_DATABASE_URL
from models import Base
from sqlalchemy import engine_from_config, pool

# 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.
if config.config_file_name is not None:
    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 = Base.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.
config.set_main_option('sqlalchemy.url', SQLALCHEMY_DATABASE_URL)

.envファイルにSQLALCHEMY_DATABASE_URLを記載します

.env
SQLALCHEMY_DATABASE_URL=postgresql://dev_user:password@db:5432/dev_db

実際にmigrationを実行してみよう!

以下のコマンドを使用することでBase.Metaを使って定義したmodelを参照して自動的にマイグレーションファイルの作成を行います

poetry run alembic revision --autogenerate -m "initial migration"
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'todos'
INFO  [alembic.autogenerate.compare] Detected added index ''ix_todos_id'' on '('id',)'
INFO  [alembic.autogenerate.compare] Detected added table 'users'
INFO  [alembic.autogenerate.compare] Detected added index ''ix_users_id'' on '('id',)'
  Generating /code/migrations/versions/21ff600fcc09_initial_migration.py ...  done
└── application
    ├── __init__.py
    ├── migrations
    │   ├── README
    │   ├── env.py
    │   ├── script.py.mako
    │   └── versions
    │       └── 21ff600fcc09_initial_migration.py
    └── alembic.ini

以下のマイグレーションファイルが作成されます

21ff600fcc09_initial_migration.py
"""initial migration

Revision ID: 21ff600fcc09
Revises: 
Create Date: 2025-02-22 05:59:10.430466

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '21ff600fcc09'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('todos',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('title', sa.String(), nullable=True),
    sa.Column('description', sa.String(), nullable=True),
    sa.Column('priority', sa.Integer(), nullable=True),
    sa.Column('complete', sa.Boolean(), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_todos_id'), 'todos', ['id'], unique=False)
    op.create_table('users',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('email', sa.String(), nullable=True),
    sa.Column('username', sa.String(), nullable=True),
    sa.Column('first_name', sa.String(), nullable=True),
    sa.Column('last_name', sa.String(), nullable=True),
    sa.Column('password', sa.String(), nullable=True),
    sa.Column('is_active', sa.Boolean(), nullable=True),
    sa.Column('role', sa.String(), nullable=True),
    sa.Column('phone_number', sa.String(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('email'),
    sa.UniqueConstraint('username')
    )
    op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
    # ### end Alembic commands ###


def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index(op.f('ix_users_id'), table_name='users')
    op.drop_table('users')
    op.drop_index(op.f('ix_todos_id'), table_name='todos')
    op.drop_table('todos')
    # ### end Alembic commands ###

以下のコマンドで作成されたマイグレーションファイルの内容をDBへ反映させます

poetry run alembic upgrade head
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 21ff600fcc09, initial migration

以下のようにマイグレーションファイルの内容がDBに反映されたら成功です

psql -U dev_user

dev_user=# \c dev_db
You are now connected to database "dev_db" as user "dev_user".
dev_db=# \dt
              List of relations
 Schema |      Name       | Type  |  Owner   
--------+-----------------+-------+----------
 public | alembic_version | table | dev_user
 public | todos           | table | dev_user
 public | users           | table | dev_user
(3 rows)

dev_db=# \d
               List of relations
 Schema |      Name       |   Type   |  Owner   
--------+-----------------+----------+----------
 public | alembic_version | table    | dev_user
 public | todos           | table    | dev_user
 public | todos_id_seq    | sequence | dev_user
 public | users           | table    | dev_user
 public | users_id_seq    | sequence | dev_user
(5 rows)

dev_user=# \d todos
                                    Table "public.todos"
   Column    |       Type        | Collation | Nullable |              Default              
-------------+-------------------+-----------+----------+-----------------------------------
 id          | integer           |           | not null | nextval('todos_id_seq'::regclass)
 title       | character varying |           |          | 
 description | character varying |           |          | 
 priority    | integer           |           |          | 
 complete    | boolean           |           |          | 
Indexes:
    "todos_pkey" PRIMARY KEY, btree (id)
    "ix_todos_id" btree (id)

dev_user=# \d users
                                    Table "public.users"
    Column    |       Type        | Collation | Nullable |              Default              
--------------+-------------------+-----------+----------+-----------------------------------
 id           | integer           |           | not null | nextval('users_id_seq'::regclass)
 email        | character varying |           |          | 
 username     | character varying |           |          | 
 first_name   | character varying |           |          | 
 last_name    | character varying |           |          | 
 password     | character varying |           |          | 
 is_active    | boolean           |           |          | 
 role         | character varying |           |          | 
 phone_number | character varying |           |          | 
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "ix_users_id" btree (id)
    "users_email_key" UNIQUE CONSTRAINT, btree (email)
    "users_username_key" UNIQUE CONSTRAINT, btree (username)

参考

0
0
0

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?