py-alembic-patterns
Alembic migration patterns for PostgreSQL. Use when creating migrations, reviewing autogenerated migrations, or handling schema changes safely.
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o py-alembic-patterns.zip https://jpskill.com/download/17842.zip && unzip -o py-alembic-patterns.zip && rm py-alembic-patterns.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17842.zip -OutFile "$d\py-alembic-patterns.zip"; Expand-Archive "$d\py-alembic-patterns.zip" -DestinationPath $d -Force; ri "$d\py-alembic-patterns.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
py-alembic-patterns.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
py-alembic-patternsフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Alembic マイグレーションのパターン
問題提起
Alembic の自動生成は便利ですが、見落としがあったり、危険なマイグレーションを生成したりすることがあります。スキーマの変更はリスクが高く、不適切なマイグレーションはデータ損失やダウンタイムを引き起こします。すべてのマイグレーションは、人間によるレビューが必要です。
パターン: マイグレーションコマンド
# モデルの変更からマイグレーションを生成
uv run alembic revision --autogenerate -m "Add user preferences table"
# マイグレーションを適用
uv run alembic upgrade head
# 1つ前のマイグレーションにロールバック
uv run alembic downgrade -1
# 特定のリビジョンにロールバック
uv run alembic downgrade abc123
# 現在のリビジョンを表示
uv run alembic current
# マイグレーション履歴を表示
uv run alembic history
# 未適用のマイグレーションを表示
uv run alembic history --indicate-current
パターン: 自動生成されたマイグレーションのレビュー
自動生成されたマイグレーションは必ずレビューしてください。修正が必要なことがよくあります。
自動生成で検出されるもの
- テーブルの作成/削除
- カラムの追加/削除
- カラムの型変更
- 外部キーの変更
- インデックスの変更 (場合による)
自動生成で見落とされるもの
- カラム名の変更 (削除 + 追加と認識され、データ損失につながる)
- テーブル名の変更 (同様の問題)
- データマイグレーション
- 制約名
- 部分インデックス
- 複雑なインデックスの変更
- チェック制約
- トリガーと関数
# ❌ 危険: カラム名変更のために自動生成されたもの
def upgrade():
op.drop_column("users", "name") # データ損失!
op.add_column("users", sa.Column("full_name", sa.String()))
# ✅ 正しい: 手動での名前変更
def upgrade():
op.alter_column("users", "name", new_column_name="full_name")
def downgrade():
op.alter_column("users", "full_name", new_column_name="name")
パターン: 安全なマイグレーション構造
"""Add user preferences table.
Revision ID: abc123
Revises: def456
Create Date: 2024-01-15 10:30:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers
revision = "abc123"
down_revision = "def456"
branch_labels = None
depends_on = None
def upgrade() -> None:
# 常に明示的に、デフォルトに頼らない
op.create_table(
"user_preferences",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("theme", sa.String(50), nullable=False, server_default="light"),
sa.Column("notifications_enabled", sa.Boolean(), nullable=False, server_default="true"),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
# 明示的なインデックス名
op.create_index(
"ix_user_preferences_user_id",
"user_preferences",
["user_id"],
)
# 明示的な名前を持つ外部キー
op.create_foreign_key(
"fk_user_preferences_user_id",
"user_preferences",
"users",
["user_id"],
["id"],
ondelete="CASCADE",
)
def downgrade() -> None:
# 常に downgrade を実装する!
op.drop_constraint("fk_user_preferences_user_id", "user_preferences", type_="foreignkey")
op.drop_index("ix_user_preferences_user_id", "user_preferences")
op.drop_table("user_preferences")
パターン: Non-Nullable なカラムの追加
問題: 既存のテーブルに NOT NULL カラムを追加すると、テーブルにデータがある場合に失敗します。
# ❌ 間違い: テーブルにデータがある場合に失敗する
def upgrade():
op.add_column("users", sa.Column("role", sa.String(50), nullable=False))
# ✅ 正しい: 3段階のプロセス
def upgrade():
# ステップ 1: nullable として追加
op.add_column("users", sa.Column("role", sa.String(50), nullable=True))
# ステップ 2: 既存の行をバックフィル
op.execute("UPDATE users SET role = 'member' WHERE role IS NULL")
# ステップ 3: NOT NULL 制約を追加
op.alter_column("users", "role", nullable=False)
def downgrade():
op.drop_column("users", "role")
パターン: データマイグレーション
問題: スキーマ変更中に既存のデータを変換する必要がある。
from sqlalchemy import text
def upgrade():
# データ操作のためのコネクションを取得
connection = op.get_bind()
# 新しいカラムを追加
op.add_column("assessments", sa.Column("status", sa.String(20)))
# データを移行
connection.execute(
text("""
UPDATE assessments
SET status = CASE
WHEN completed_at IS NOT NULL THEN 'completed'
WHEN started_at IS NOT NULL THEN 'in_progress'
ELSE 'pending'
END
""")
)
# これで NOT NULL を安全に追加できる
op.alter_column("assessments", "status", nullable=False)
def downgrade():
op.drop_column("assessments", "status")
パターン: 大規模テーブルのマイグレーション
問題: 大規模テーブルのマイグレーションは、テーブルを長時間ロックする可能性があります。
def upgrade():
# ✅ 正しい: インデックスを同時に追加 (ロックなし)
op.execute(
"CREATE INDEX CONCURRENTLY ix_events_user_id ON events (user_id)"
)
# 注: CONCURRENTLY は autocommit モードが必要です
# マイグレーションファイルに追加:
# from alembic import context
# context.configure(transaction_per_migration=False)
def downgrade():
op.execute("DROP INDEX CONCURRENTLY IF EXISTS ix_events_user_id")
# 大規模テーブルのカラム変更については、以下を検討してください。
# 1. 新しいカラムを追加 (nullable)
# 2. 別のスクリプトでバッチ処理でバックフィル
# 3. 別のマイグレーションで制約を追加
パターン: Enum の変更
問題: PostgreSQL の enum は変更が難しい。
# 既存の enum に値を追加する
def upgrade():
# PostgreSQL 固有: enum に値を追加
op.execute("ALTER TYPE assessment_status ADD VALUE 'archived'")
def downgrade():
# PostgreSQL では enum の値を削除できません!
# 選択肢:
# 1. そのままにする (通常は問題ない)
# 2. enum を再作成する (複雑、データマイグレーションが必要)
pass
# 新しい enum を作成する
def upgrade():
# 最初に enum 型を作成
assessment_status = postgresql.ENUM(
"draft", "active", "completed",
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Alembic Migration Patterns
Problem Statement
Alembic autogenerate is convenient but misses things and sometimes generates dangerous migrations. Schema changes are high-risk - bad migrations cause data loss or downtime. Every migration needs human review.
Pattern: Migration Commands
# Generate migration from model changes
uv run alembic revision --autogenerate -m "Add user preferences table"
# Apply migrations
uv run alembic upgrade head
# Rollback one migration
uv run alembic downgrade -1
# Rollback to specific revision
uv run alembic downgrade abc123
# Show current revision
uv run alembic current
# Show migration history
uv run alembic history
# Show pending migrations
uv run alembic history --indicate-current
Pattern: Reviewing Autogenerated Migrations
ALWAYS review autogenerated migrations. They often need fixes.
What Autogenerate Catches
- Table creation/deletion
- Column addition/removal
- Column type changes
- Foreign key changes
- Index changes (sometimes)
What Autogenerate Misses
- Column renames (sees as drop + add = DATA LOSS)
- Table renames (same problem)
- Data migrations
- Constraint names
- Partial indexes
- Complex index changes
- Check constraints
- Triggers and functions
# ❌ DANGEROUS: Autogenerated for column rename
def upgrade():
op.drop_column("users", "name") # DATA LOSS!
op.add_column("users", sa.Column("full_name", sa.String()))
# ✅ CORRECT: Manual rename
def upgrade():
op.alter_column("users", "name", new_column_name="full_name")
def downgrade():
op.alter_column("users", "full_name", new_column_name="name")
Pattern: Safe Migration Structure
"""Add user preferences table.
Revision ID: abc123
Revises: def456
Create Date: 2024-01-15 10:30:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers
revision = "abc123"
down_revision = "def456"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Always explicit, never rely on defaults
op.create_table(
"user_preferences",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("theme", sa.String(50), nullable=False, server_default="light"),
sa.Column("notifications_enabled", sa.Boolean(), nullable=False, server_default="true"),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
# Explicit index names
op.create_index(
"ix_user_preferences_user_id",
"user_preferences",
["user_id"],
)
# Foreign key with explicit name
op.create_foreign_key(
"fk_user_preferences_user_id",
"user_preferences",
"users",
["user_id"],
["id"],
ondelete="CASCADE",
)
def downgrade() -> None:
# Always implement downgrade!
op.drop_constraint("fk_user_preferences_user_id", "user_preferences", type_="foreignkey")
op.drop_index("ix_user_preferences_user_id", "user_preferences")
op.drop_table("user_preferences")
Pattern: Adding Non-Nullable Columns
Problem: Adding NOT NULL column to existing table fails if table has rows.
# ❌ WRONG: Fails if table has data
def upgrade():
op.add_column("users", sa.Column("role", sa.String(50), nullable=False))
# ✅ CORRECT: Three-step process
def upgrade():
# Step 1: Add as nullable
op.add_column("users", sa.Column("role", sa.String(50), nullable=True))
# Step 2: Backfill existing rows
op.execute("UPDATE users SET role = 'member' WHERE role IS NULL")
# Step 3: Add NOT NULL constraint
op.alter_column("users", "role", nullable=False)
def downgrade():
op.drop_column("users", "role")
Pattern: Data Migrations
Problem: Need to transform existing data during schema change.
from sqlalchemy import text
def upgrade():
# Get connection for data operations
connection = op.get_bind()
# Add new column
op.add_column("assessments", sa.Column("status", sa.String(20)))
# Migrate data
connection.execute(
text("""
UPDATE assessments
SET status = CASE
WHEN completed_at IS NOT NULL THEN 'completed'
WHEN started_at IS NOT NULL THEN 'in_progress'
ELSE 'pending'
END
""")
)
# Now safe to add NOT NULL
op.alter_column("assessments", "status", nullable=False)
def downgrade():
op.drop_column("assessments", "status")
Pattern: Large Table Migrations
Problem: Migrations on large tables can lock the table for too long.
def upgrade():
# ✅ CORRECT: Add index concurrently (no lock)
op.execute(
"CREATE INDEX CONCURRENTLY ix_events_user_id ON events (user_id)"
)
# Note: CONCURRENTLY requires autocommit mode
# Add to migration file:
# from alembic import context
# context.configure(transaction_per_migration=False)
def downgrade():
op.execute("DROP INDEX CONCURRENTLY IF EXISTS ix_events_user_id")
# For column changes on large tables, consider:
# 1. Add new column (nullable)
# 2. Backfill in batches via separate script
# 3. Add constraint in separate migration
Pattern: Enum Changes
Problem: PostgreSQL enums are tricky to modify.
# Adding a value to existing enum
def upgrade():
# PostgreSQL-specific: Add value to enum
op.execute("ALTER TYPE assessment_status ADD VALUE 'archived'")
def downgrade():
# Can't remove enum values in PostgreSQL!
# Options:
# 1. Leave it (usually fine)
# 2. Recreate enum (complex, requires data migration)
pass
# Creating new enum
def upgrade():
# Create enum type first
assessment_status = postgresql.ENUM(
"draft", "active", "completed", "archived",
name="assessment_status",
create_type=True,
)
assessment_status.create(op.get_bind())
# Then use it
op.add_column(
"assessments",
sa.Column("status", assessment_status, nullable=False, server_default="draft"),
)
def downgrade():
op.drop_column("assessments", "status")
op.execute("DROP TYPE assessment_status")
Pattern: Multiple Heads (Branching)
Problem: Multiple developers creating migrations simultaneously.
# Check for multiple heads
uv run alembic heads
# If multiple heads, create merge migration
uv run alembic merge -m "Merge heads" abc123 def456
# Or specify down_revision as tuple
down_revision = ("abc123", "def456")
Pattern: Testing Migrations
# test_migrations.py
import pytest
from alembic import command
from alembic.config import Config
@pytest.fixture
def alembic_config():
config = Config("alembic.ini")
return config
def test_upgrade_downgrade(alembic_config, test_db):
"""Test migrations can upgrade and downgrade."""
# Upgrade to head
command.upgrade(alembic_config, "head")
# Downgrade to base
command.downgrade(alembic_config, "base")
# Upgrade again
command.upgrade(alembic_config, "head")
def test_migration_has_downgrade():
"""Ensure all migrations have downgrade."""
# Parse migration files and check downgrade isn't just 'pass'
...
Migration Review Checklist
Before applying any migration:
- [ ] Downgrade function implemented (not just
pass) - [ ] Column renames use
alter_column, not drop+add - [ ] Non-nullable columns added with default or backfill
- [ ] Large table operations consider locking
- [ ] Indexes have explicit names
- [ ] Foreign keys have explicit names and ON DELETE behavior
- [ ] Enums created before use
- [ ] Data migrations tested with real data volumes
- [ ] Migration tested: upgrade, downgrade, upgrade
Production Safety
# Set statement timeout to prevent long locks
def upgrade():
op.execute("SET statement_timeout = '5s'")
# Your migration here
op.execute("SET statement_timeout = '0'") # Reset
# Always backup before production migrations
pg_dump -h host -U user -d dbname > backup_before_migration.sql
# Apply with --sql to preview
uv run alembic upgrade head --sql
# Apply for real
uv run alembic upgrade head
Common Issues
| Issue | Likely Cause | Solution |
|---|---|---|
| "Target database is not up to date" | Pending migrations | Run alembic upgrade head |
| "Can't locate revision" | Missing migration file | Check version history |
| Multiple heads | Concurrent development | Create merge migration |
| Lock timeout | Long-running migration | Use CONCURRENTLY, batch updates |
| Data loss on deploy | Column rename as drop+add | Review autogenerated carefully |