Loading...
Loading...
Advanced SQLModel patterns and comprehensive database migrations with Alembic. Use when creating SQLModel models, defining relationships (one-to-many, many-to-many, self-referential), setting up database migrations, optimizing queries, solving N+1 problems, implementing inheritance patterns, working with composite keys, creating indexes, performing data migrations, or troubleshooting Alembic issues. Triggers include "SQLModel", "Alembic migration", "database model", "relationship", "foreign key", "migration", "N+1 query", "query optimization", "database schema", or questions about ORM patterns.
npx skill4agent add bilalmk/todo_correct sqlmodel-expertfrom sqlmodel import Field, SQLModel
from typing import Optional
from datetime import datetime
class Task(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
title: str = Field(index=True)
description: Optional[str] = None
completed: bool = Field(default=False)
created_at: datetime = Field(default_factory=datetime.utcnow)# Using provided script
python scripts/init_db.py --url postgresql://user:pass@localhost/db
# Or manually
from sqlmodel import create_engine
engine = create_engine("postgresql://user:pass@localhost/db")
SQLModel.metadata.create_all(engine)# Using provided helper script
./scripts/migrate.sh create "add user table"
# Or directly with Alembic
alembic revision --autogenerate -m "add user table"
alembic upgrade headfrom typing import List
from sqlmodel import Field, Relationship, SQLModel
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
# One team has many heroes
heroes: List["Hero"] = Relationship(back_populates="team")
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
team_id: Optional[int] = Field(foreign_key="team.id")
# Many heroes belong to one team
team: Optional[Team] = Relationship(back_populates="heroes")class HeroTeamLink(SQLModel, table=True):
hero_id: int = Field(foreign_key="hero.id", primary_key=True)
team_id: int = Field(foreign_key="team.id", primary_key=True)
joined_at: datetime = Field(default_factory=datetime.utcnow)
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
teams: List["Team"] = Relationship(
back_populates="heroes",
link_model=HeroTeamLink
)
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
heroes: List[Hero] = Relationship(
back_populates="teams",
link_model=HeroTeamLink
)from sqlalchemy.orm import selectinload
# BAD - N+1 queries
users = session.exec(select(User)).all()
for user in users:
posts = user.posts # Each triggers a query!
# GOOD - Eager loading (2 queries total)
statement = select(User).options(selectinload(User.posts))
users = session.exec(statement).all()
for user in users:
posts = user.posts # No additional query!# 1. Modify your model
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
email: str
phone: str # New field added
# 2. Generate migration
# alembic revision --autogenerate -m "add phone to user"
# 3. Review generated migration
def upgrade() -> None:
op.add_column('user', sa.Column('phone', sa.String(), nullable=True))
def downgrade() -> None:
op.drop_column('user', 'phone')
# 4. Apply migration
# alembic upgrade headpython scripts/init_db.py --url postgresql://user:pass@localhost/db./scripts/migrate.sh init # Initialize Alembic
./scripts/migrate.sh create "message" # Create migration
./scripts/migrate.sh upgrade # Apply migrations
./scripts/migrate.sh downgrade # Rollback one
./scripts/migrate.sh current # Show current
./scripts/migrate.sh history # Show history
./scripts/migrate.sh test # Test up & downassets/example-models.pycp assets/example-models.py your-project/app/models.pyback_populates# Solution: Ensure models are imported in env.py
from app.models import User, Task, Team # Import all models
target_metadata = SQLModel.metadata# Check current state
alembic current
# Manually fix issue, then stamp
alembic stamp head
# Or downgrade and retry
alembic downgrade -1
alembic upgrade head# Enable query logging
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
# Use EXPLAIN ANALYZE
explain = session.exec(text("EXPLAIN ANALYZE SELECT ...")).all()
# Profile queries
# See references/queries-optimization.md for detailed patterns# Use selectinload
statement = select(User).options(selectinload(User.posts))
# Or joinedload
from sqlalchemy.orm import joinedload
statement = select(User).options(joinedload(User.posts))./scripts/migrate.sh create "description"./scripts/migrate.sh testalembic upgrade headpg_dump mydb > backup.sqlalembic upgrade head# Phase 1: Add new column (nullable)
def upgrade():
op.add_column('user', sa.Column('new_email', sa.String(), nullable=True))
# Deploy app version that writes to both columns
# Phase 2: Backfill data
def upgrade():
op.execute("UPDATE user SET new_email = email WHERE new_email IS NULL")
# Phase 3: Make non-nullable
def upgrade():
op.alter_column('user', 'new_email', nullable=False)
# Deploy app version that reads from new column
# Phase 4: Drop old column
def upgrade():
op.drop_column('user', 'email')