Loading...
Loading...
Expert guidance for SQLModel - the Python library combining SQLAlchemy and Pydantic for database models. Use when (1) creating database models that work as both SQLAlchemy ORM and Pydantic schemas, (2) building FastAPI apps with database integration, (3) defining model relationships (one-to-many, many-to-many), (4) performing CRUD operations with type safety, (5) setting up async database sessions, (6) integrating with Alembic migrations, (7) handling model inheritance and mixins, or (8) converting between database models and API schemas.
npx skill4agent add salmanferozkhan/cloud-and-fast-api sqlmodelpip install sqlmodelfrom sqlmodel import Field, SQLModel, Session, create_engine, select
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
age: int | None = None
engine = create_engine("sqlite:///database.db")
SQLModel.metadata.create_all(engine)
# Create
with Session(engine) as session:
hero = Hero(name="Spider-Boy", age=18)
session.add(hero)
session.commit()
session.refresh(hero)
# Read
with Session(engine) as session:
heroes = session.exec(select(Hero)).all()| Concept | Description |
|---|---|
| Makes class a database table (without it, it's just Pydantic) |
| Define column attributes: |
| Database session for CRUD operations |
| Type-safe query builder |
| Define relationships between models |
class HeroBase(SQLModel):
name: str
age: int | None = Noneclass Hero(HeroBase, table=True):
id: int | None = Field(default=None, primary_key=True)class HeroCreate(HeroBase):
secret_name: str
class HeroPublic(HeroBase):
id: int
class HeroUpdate(SQLModel):
name: str | None = None
age: int | None = Nonedef create_hero(session: Session, hero: HeroCreate) -> Hero:
db_hero = Hero.model_validate(hero)
session.add(db_hero)
session.commit()
session.refresh(db_hero)
return db_herodef get_hero(session: Session, hero_id: int) -> Hero | None:
return session.get(Hero, hero_id)
def get_heroes(session: Session, skip: int = 0, limit: int = 100) -> list[Hero]:
return session.exec(select(Hero).offset(skip).limit(limit)).all()def update_hero(session: Session, hero_id: int, hero_update: HeroUpdate) -> Hero | None:
db_hero = session.get(Hero, hero_id)
if not db_hero:
return None
hero_data = hero_update.model_dump(exclude_unset=True)
db_hero.sqlmodel_update(hero_data)
session.add(db_hero)
session.commit()
session.refresh(db_hero)
return db_herodef delete_hero(session: Session, hero_id: int) -> bool:
hero = session.get(Hero, hero_id)
if not hero:
return False
session.delete(hero)
session.commit()
return Truefrom sqlmodel import SQLModel, Session, create_engine
DATABASE_URL = "sqlite:///./database.db"
engine = create_engine(DATABASE_URL, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def get_session():
with Session(engine) as session:
yield sessionfrom typing import Annotated
from fastapi import Depends
SessionDep = Annotated[Session, Depends(get_session)]
@app.post("/heroes/", response_model=HeroPublic)
def create_hero(hero: HeroCreate, session: SessionDep):
db_hero = Hero.model_validate(hero)
session.add(db_hero)
session.commit()
session.refresh(db_hero)
return db_herofrom contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
create_db_and_tables()
yield
app = FastAPI(lifespan=lifespan)| Topic | File | When to Use |
|---|---|---|
| Models | models.md | Field options, validators, computed fields, inheritance, mixins |
| Relationships | relationships.md | One-to-many, many-to-many, self-referential, lazy loading |
| Async | async.md | Async sessions, async engine, background tasks |
| Migrations | migrations.md | Alembic setup, auto-generation, migration patterns |
# All heroes
heroes = session.exec(select(Hero)).all()
# Single result (first or None)
hero = session.exec(select(Hero).where(Hero.name == "Spider-Boy")).first()
# Get by primary key
hero = session.get(Hero, 1)from sqlmodel import select, or_, and_
# Single condition
select(Hero).where(Hero.age >= 18)
# Multiple conditions (AND)
select(Hero).where(Hero.age >= 18, Hero.name == "Spider-Boy")
# OR conditions
select(Hero).where(or_(Hero.age < 18, Hero.age > 60))
# LIKE/contains
select(Hero).where(Hero.name.contains("Spider"))select(Hero).order_by(Hero.name)
select(Hero).order_by(Hero.age.desc())
select(Hero).offset(10).limit(5)table=Truemodel_validate()sqlmodel_update()exclude_unset=Truemodel_dump()Field()Annotated@app.on_eventField(index=True)echo=True| Issue | Solution |
|---|---|
Missing | Add |
| Circular imports | Use |
| Session already closed | Ensure session is still open when accessing lazy-loaded relationships |
| Migration not detecting changes | Use |