SQLAlchemy | 2.0.20 |
---|---|
alembic | 1.12.0 |
Python | 3.11.5 |
SQLAlchemy
で、alembic
を使ったmigrationを行う方法を紹介します。@tiangolo
)が開発しているSQLModelというORMもあります。しかしながら、まだ発展途上のため、安定感のあるSQLAlchemy
で進めていきます。alembic
メインの内容とするため、SQLAlchemy
は導入済みの前提で進めます。pip
でインストールできます。$ pip install alembic
migrations
はディレクトリ名になるため、別の名称が良い場合は変えられます。$ alembic init migrations
$ tree
.
├── alembic.ini
├── migrations
│ ├── README
│ ├── env.py
│ ├── script.py.mako
│ └── versions
alembic.ini
: ログやDB設定などが記載された設定ファイルmigrations
: migration関連のファイルのディレクトリREADME
: migration関連のメモenv.py
: migrationの設定ファイルscript.py.mako
: テンプレートエンジンMako
を使ったmigrationファイルのテンプレversions
: migrationファイルが保存されるディレクトリalembic.ini
ファイルを編集します。sqlalchemy.url
という項目があるので、その部分を書き換えます。今回はMySQLにつなぐため、MySQLへの接続情報を記載します。sqlalchemy.url = driver://user:pass@localhost/dbname
env.py
へmigration実行時に使える変数として設定します。config.set_main_option('sqlalchemy.url', "接続情報")
のようにすることで、sqlalchemy.url
が上書きされた状態で実行できます。config = context.config
# ↓を追記
config.set_main_option('sqlalchemy.url', os.environ['DB_URL'])
.env
ファイルを作成し、以下のようにデータベース接続情報を記載しましょう。DB_URL=mysql://root:password@localhost/dbname
alembic.ini
のsqlalchemy.url
は必要なくなるため、コメントアウトしてしまいましょう。# sqlalchemy.url = mysql://user:pass@localhost/dbname
alembic.ini
にコメントアウトされたfile_template
という項目があるので、コメントを外しておきましょう。file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
.
├── alembic.ini
├── migrations
│ ├── README
│ ├── env.py
│ ├── script.py.mako
│ └── versions
├── src
│ ├── __init__.py
│ ├── models
│ │ ├── __init__.py
│ │ ├── base.py
│ │ └── users.py
base.py
を作成します。from datetime import datetime
from sqlalchemy import String, DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.sql.functions import current_timestamp
import uuid
class Base(DeclarativeBase):
pass
class BaseModelMixin:
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=uuid.uuid4)
created_at: Mapped[datetime] = mapped_column(DateTime,
nullable=False,
server_default=current_timestamp())
updated_at: Mapped[datetime] = mapped_column(DateTime,
nullable=False,
default=current_timestamp(),
onupdate=func.now())
deleted_at: Mapped[datetime] = mapped_column(DateTime, nullable=True)
class Base(DeclarativeBase)
で事足りますが、Mix-in(共通で使い回す定義)を定義しておくと便利です。users.py
も作りましょう。from datetime import datetime
from sqlalchemy import String, Text, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from src.models.base import Base, BaseModelMixin
class User(BaseModelMixin, Base):
__tablename__ = 'users'
mysql_charset = ('utf8mb4',)
mysql_collate = 'utf8mb4_unicode_ci'
username: Mapped[str] = mapped_column(String(255), nullable=True, unique=True)
email: Mapped[str] = mapped_column(String(255), nullable=False, unique=True, index=True)
password: Mapped[str] = mapped_column(Text, nullable=False)
email_verified_at: Mapped[datetime] = mapped_column(DateTime, nullable=True)
last_login_at: Mapped[datetime] = mapped_column(DateTime, nullable=True)
models
をimportしたときに便利なように__init__.py
も忘れずに書きましょう。from .users import User
env.py
のtarget_metadata
を編集します。from src.models.base import Base
target_metadata = Base.metadata
$ source .env && alembic revision --autogenerate -m 'create-users-table'
migrations/versions
にmigrationファイルが生成されます。$ alembic upgrade head
$ alembic upgrade head
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> xxxxxxxx, create-users-table
users
テーブルがMySQLのデータベース上に作成されたことがわかります。src/models/users.py
を編集します。今回はavatar
(ユーザーの画像URL)を格納するカラムを追加します。class User(BaseModelMixin, Base):
__tablename__ = 'users'
mysql_charset = ('utf8mb4',)
mysql_collate = 'utf8mb4_unicode_ci'
username: Mapped[str] = mapped_column(String(255), nullable=True, unique=True)
email: Mapped[str] = mapped_column(String(255), nullable=False, unique=True, index=True)
avatar: Mapped[str] = mapped_column(String(255), nullable=True) # <- これを追加
password: Mapped[str] = mapped_column(Text, nullable=False)
email_verified_at: Mapped[datetime] = mapped_column(DateTime, nullable=True)
last_login_at: Mapped[datetime] = mapped_column(DateTime, nullable=True)
email
とpassword
の間に挟み込みます。alembic revision
を実行しましょう。$ alembic revision --autogenerate -m 'add-column-avatar-into-users'
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'users.avatar'
Generating /app/migrations/versions/2023_09_07_0618-9c5881a40e10_add_column_avatar_into_users.py ... done
Detected added column 'users.avatar'
と書いてあるのがわかります。「users.avatar
が追加されたことを検知した」という意味です。def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('avatar', sa.String(length=255), nullable=True))
# ### end Alembic commands ###
alembic
にはbatch
(一群の意味)モードというまとめて実行するモードがあります。そのモードに含まれるadd_column
にはinsert_after
という順序を制御するオプションがあるので、それを使いましょう。(なぜ通常のadd_column
にないのか不明)def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', recreate='always') as batch_op:
batch_op.add_column(sa.Column('avatar', sa.String(length=255), nullable=True),
insert_after='email')
# ### end Alembic commands ###
email
の後にavatar
があることを確認できます。mysql> show columns from users;
--------------
show columns from users
--------------
+-------------------+--------------+------+-----+-------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-----+-------------------+-------------------+
| username | varchar(255) | YES | UNI | NULL | |
| email | varchar(255) | NO | UNI | NULL | |
| avatar | varchar(255) | YES | | NULL | |
| password | text | NO | | NULL | |
| email_verified_at | datetime | YES | | NULL | |
| last_login_at | datetime | YES | | NULL | |
| id | varchar(32) | NO | PRI | NULL | |
| created_at | datetime | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| updated_at | datetime | NO | | NULL | |
| deleted_at | datetime | YES | | NULL | |
+-------------------+--------------+------+-----+-------------------+-------------------+
$ alembic history
alembic history
5ba7c3ae1200 -> 9c5881a40e10 (head), add-column-avatar-into-users
<base> -> 5ba7c3ae1200, create-users-table
alembic
は独自のコマンドがあり設定項目も多く、覚えるのに苦労します。高機能ゆえのデメリットとも言えます。alembic
を使いこなして、適切なデータベース管理をしていきましょう。