FastAPI + SQLAlchemy + Alembic + SQLiteでマイグレーション


press
FastAPI + SQLAlchemy + Alembic +...

FastAPI + SQLAlchemy + Alembic + SQLiteでマイグレーション

FastAPIにはデータベースのマイグレーション機能はありませんが、Alembicというデータベースのマイグレーションツールがあり、マイグレーションファイルを自動で生成できます。本記事ではORMのSQLAlchemyと組み合わせてデータベースのマイグレーションの仕方について解説します。

開発環境

PC: MacBook Pro (14, 2021)
OS: macOS Monterey 12.6.7
Python: 3.10.0
FastAPI: 0.104.1
Alembic: 1.13.0
SQLAlchemy: 2.0.23
SQLAlchemy-Utils: 0.41.1

ディレクトリ構造

.
├── alembic.ini
├── database.py
├── migrations
│   ├── README
│   ├── env.py
│   ├── script.py.mako
│   └── versions
│       ├── 2637abbcf5c1_create_tables.py
│       └── d75d243cebd5_add_age_column.py
├── models
│   ├── mixins.py
│   └── users.py
├── test.db
└── test_main.http

パッケージのインストール

FastAPIは既にインストール済みの状態で、マイグレーションに必要なパッケージのインストールを下記のコマンドで実行します。

pip install alembic SQLAlchemy SQLAlchemy-Utils

Alembic:SQLAlchemyのマイグレーションツール
SQLAlchemy:データベースを操作するORM(Object Relational Mapper)
SQLAlchemy-Utils:SQLAlchemyのユーティリティ関数

alembicのセットアップ

下記のコマンドを実行すると、alembic.ini、env.pyなどが入ったmigrationsというディレクトリが生成されます。

alembic init migrations

treeでディクトリ構造を表示させると下記のようになっています。

.
├── alembic.ini
└── migrations
    ├── README
    ├── env.py
    ├── script.py.mako
    └── versions

alembic.iniの修正

alembic.ini

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

sqlalchemy.url = sqlite:///test.db


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

database.pyの修正

データベースの設定を行うdatabase.pyを作成します。

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

# データベースURL(SQLiteの場合)
SQLALCHEMY_DATABASE_URL = "sqlite:///test.db"

# SQLAlchemy エンジンの作成
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)

# セッションの作成
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()


def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

モデルの作成

modelsディレクトリにユーザー情報のusers.pyと共通機能のmixins.pyの2つのSQLAlchemyのモデルを作成します。マイグレーションの確認だけであればusers.pyのみで問題ないですが、実際のアプリケーション開発を想定して2つのモデルを用意します。

models/users.py

from sqlalchemy import Column, Integer, String
from sqlalchemy_utils import UUIDType
from uuid import uuid4
from database import Base
from .mixins import TimestampMixin


class User(Base, TimestampMixin):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    uuid = Column(UUIDType(binary=False), nullable=False, default=uuid4)
    name = Column(String)
    fullname = Column(String)

mixins.pyのTimestampMixinを継承しているので作成日時と更新日時のカラムも作成されます。

models/mixins.py

from sqlalchemy import Column, DateTime, func


class TimestampMixin(object):
    created_at = Column(DateTime, default=func.now(), nullable=False, comment="作成日時")
    updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False, comment="更新日時")

TimestampMixinはusers.pyのUserクラスのように継承させることで作成日時と更新日時のカラムを作成します。

env.pyの修正

from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context

# 追記
from database import Base
import models.users  # noqa: F401
import models.mixins  # noqa: F401

...

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

# noqa: F401はエラー対策用に記載

マイグレーションファイルの作成

下記のコマンドでcreate tablesという名前のマイグレーションファイルを作成します。

alembic revision --autogenerate -m "create tables"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'users'

migrations/versionsにマイグレーションファイル(2637abbcf5c1_create_tables.py)が生成されていれば成功です。

マイグレーションの実行

マイグレーションファイルの変更

今回はUserクラスのuuidカラムにUUIDを指定しているため、そのままalembic upgrade headでマイグレーションを実行すると下記のエラーが発生してしまうので予め修正しておきます。(UUIDを使用していない場合はsqlalchemy_utilsのインポートは必要ありません。)

sa.Column('uuid', sqlalchemy_utils.types.uuid.UUIDType(binary=False), nullable=False),
NameError: name 'sqlalchemy_utils' is not defined

2637abbcf5c1_create_tables.py(alembic revision –autogenerate -m “create tables”を実行した際にmigrations/versions内に生成されたマイグレーションファイル)

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
# 追記
import sqlalchemy_utils
...

import sqlalchemy_utilsを追記します。

マイグレーションの実行

改めて下記のコマンドでマイグレーションを実行してデータベースに内容を反映します。

alembic upgrade head
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 2637abbcf5c1, create tables

テーブルが追加されました。
エディタのツールやデータベースのアプリなどで画像のようにusersテーブルが確認できればマイグレーション成功です。

追加されたusersテーブル
追加されたusersテーブル

カラムの追加

users.pyにageカラムを追記してマイグレーションファイルを作成します。

    fullname = Column(String)
    # 追加
    age = Column(Integer)
alembic revision --autogenerate -m "add age column"

migrations/versionsにマイグレーションファイル(d75d243cebd5_add_age_column.py)が生成されていれば成功です。続けて下記のコマンドでマイグレーションを実行します。

alembic upgrade head
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 2637abbcf5c1, create tables
INFO  [alembic.runtime.migration] Running upgrade 2637abbcf5c1 -> d75d243cebd5, add age column

画像のようにageカラムが追加されていれば成功です。

alembicにるマイグレーション
alembicにるマイグレーション

ロールバック

データベースを前の状態に戻したいときは下記のコマンドで戻せます。-1で1つ前の状態、-2で2つ前の状態に戻せます。反対に新しい状態にする際は+1、+2で指定できます。

alembic downgrade -1
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running downgrade d75d243cebd5 -> 2637abbcf5c1, add age column

画像のようにageカラムの追加前の状態に戻せていれば成功です。

alembicにるマイグレーション
alembicにるマイグレーション

補足

上記以外のコマンドについて

現在のバージョンを出力

alembic current

出力結果
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
d75d243cebd5 (head)

リビジョンID、元のリビジョンID、作成日などを出力

alembic history --verbose

出力結果
Rev: d75d243cebd5 (head)
Parent: 2637abbcf5c1
Path: /DIRPATH/migrations/versions/d75d243cebd5_add_age_column.py

    add age column
    
    Revision ID: d75d243cebd5
    Revises: 2637abbcf5c1
    Create Date: 2023-12-05 13:37:36.048555

...

データベースの初期化

alembic downgrade base

株式会社ファントムへのお問い合わせ

群馬県でPythonを使ったAIやソフトウェアを開発している株式会社ファントムが運営しています。




    Related Articles

    FastAPI

    tcp 0.0.0.0:5000: bind: address already in use

    tcp 0.0.0.0:5000: bind: address already in use FlaskやFastAPIで開発することも増えてきたので5000番のポートを使用する機会が増えました。Djangoで開発する時 […]

    Posted on by press
    FastAPI

    コピペでOK! エラー検知・監視でSentryを導入

    コピペでOK! エラー検知・監視でSentryを導入 システムを運用していくうえで避けては通れないシステムの監視とエラーの検知ですが、ファントムではSentryというサービスを利用しています。SentryとSlackを連 […]

    Posted on by press

    最新情報をお届けします!

    メーリングリストに登録するとファントムの最新情報をお届けします

    お客様のメールアドレスを共有することはありません