参考
https://www.sria.co.jp/blog/2021/06/5557/
マイグレーションツールについて
FastAPIとかのフレームワークを使う際にでDB関係をいじるときは、自分でmigrationツールとORMを選ぶ必要があるんですが、migrationツールの選定は注意が必要っぽいです。
ORMでsqlalchemyを使うならsqlalchemy-migrateではなくalembicを使った方がいいらしいです。
SQLAlchemy-migrateは第三者が作ってるものらしくて、alembicはSQLAlchemyの作者(Mike Bayer)が考える最強のMigrationツールとして Alembicを作ったらしいです
名前的にはSQLAlchemy-migrateがオフィシャルっぽい感じがするのでややこしいですねw
- https://stackoverflow.com/questions/4209705/is-it-worth-using-sqlalchemy-migrate
- https://groups.google.com/g/sqlalchemy/c/eF1DE20FMCQ
どうやら経緯としては
- SQLAlchemy-migrate が第三者によって作られる。
- メンテする人がいなくなり、 SQLAlchemy の作者(Mike Bayer)がメンテナーみたいな風潮になる
- その Mike Bayer は、自分が考える最強の Migration ツールとして Alembic を作る
らしいですw
使い方
事前にpostgresとかのセッティングはしておきましょう。
今回自分はpostgresを使ってます
alembic init test
このあと、databse.pyとかのファイルを適当に作ってDB接続エンジンを作ります
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('postgresql://username:password@localhost:5432/onsen_mania')
Base = declarative_base()
そのあとmodels.pyを作成してモデルの設定を記述していきます
from sqlalchemy import Column, DateTime, Integer, String, Text, Float, Boolean
from sqlalchemy.ext.declarative import declared_attr
from datetime import datetime
from database import Base
class TimestampMixin(object):
@declared_attr
def created_at(cls):
return Column(DateTime, default=datetime.now(), nullable=False)
@declared_attr
def updated_at(cls):
return Column(
DateTime, default=datetime.now(), onupdate=datetime.now(), nullable=False
)
class Onsen(Base, TimestampMixin):
__tablename__ = 'onsen'
id = Column(Integer, primary_key=True)
name = Column(String)
address = Column(String)
tel = Column(String)
open_time = Column(String)
off_day = Column(String)
parking = Column(String)
price = Column(String)
caution = Column(Text)
sauna = Column(String)
image_path = Column(String)
lat = Column(Float)
lon = Column(Float)
pref = Column(String)
def __repr__(self):
return "<Onsen('name={}', address={}, tel={}, open_time={}, off_day={}, parking={}, price={}, caution={}, sauna={}, image_path={}, lat={}, lon={})>".format(
self.name,
self.address,
self.tel,
self.open_time,
self.off_day,
self.parking,
self.price,
self.caution,
self.sauna,
self.image_path,
self.lat,
self.lon,
self.pref
)
ざっくりこんな感じ
そのあと、alembic.iniの設定をする
基本は上で作ったエンジンと書き方は同じ。ドキュメントや上の記事を参照
env.pyでモデルを読み込む
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from models import Onsen, Users
# 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 database import Base
target_metadata = Base.metadata
# target_metadata = None
# 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() -> None:
"""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() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
url = config.get_main_option("sqlalchemy.url")
with connectable.connect() as connection:
context.configure(
connection=connection, url=url, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
ここまでできれば
alembic revision --autogenerate -m "create tables"
alembic upgrade head # create
alembic downgrade base # erase all
これで逐次進捗を確認するとテーブルが作成されているのが確認できるはず
テーブルを追加したりするときは、models.pyで変更を加えるとか、新しいクラスをフォーマットに従って定義して上のコマンドを打つとマイグレーションスクリプトが作成されるので超便利
すごいなぁ