Loading...
Loading...
This skill should be used when the user asks to "use SQLModel", "define SQLModel models", "connect SQLModel with FastAPI", "set up a database with SQLModel", or needs guidance on SQLModel best practices, relationships, or session management.
npx skill4agent add the-perfect-developer/the-perfect-opencode sqlmodelpip install sqlmodelpip install sqlmodel psycopg2-binary # PostgreSQL
pip install sqlmodel pymysql # MySQLSQLModeltable=TrueSQLModeltable=Truefrom sqlmodel import Field, SQLModel
# Data model only — no table created
class HeroBase(SQLModel):
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
# Table model — creates the `hero` table
class Hero(HeroBase, table=True):
id: int | None = Field(default=None, primary_key=True)int | NoneOptional[int]int | None = Field(default=None, primary_key=True)Noneclass Article(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
title: str
body: str
views: int | None = None # nullable column, default NULLfrom sqlmodel import SQLModel, create_engine
DATABASE_URL = "sqlite:///database.db"
engine = create_engine(DATABASE_URL)
# PostgreSQL example:
# engine = create_engine("postgresql+psycopg2://user:pass@host/db")echo=TrueSQLModel.metadata.create_all(engine)SQLModel.metadata# db.py
from sqlmodel import SQLModel, create_engine
from . import models # import models before create_all
engine = create_engine("sqlite:///database.db")
def create_db_and_tables() -> None:
SQLModel.metadata.create_all(engine)create_allwith.close()withfrom sqlmodel import Session
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
session.add(hero)
session.commit()
session.refresh(hero) # populates auto-generated fields like id
print(hero.id) # now has the DB-assigned valueyieldfrom fastapi import Depends
from sqlmodel import Session
def get_session():
with Session(engine) as session:
yield session
@app.post("/heroes/", response_model=HeroPublic)
def create_hero(
*,
session: Session = Depends(get_session),
hero: HeroCreate,
) -> HeroPublic:
db_hero = Hero.model_validate(hero)
session.add(db_hero)
session.commit()
session.refresh(db_hero)
return db_heroyieldwith| Model | Purpose | |
|---|---|---|
| Shared fields (base data model) | No |
| DB table model | Yes |
| API input — no | No |
| API output — required | No |
| Partial update — all fields optional | No |
class HeroBase(SQLModel):
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
class Hero(HeroBase, table=True):
id: int | None = Field(default=None, primary_key=True)
class HeroCreate(HeroBase):
pass # same fields as HeroBase, named explicitly for clarity
class HeroPublic(HeroBase):
id: int # required in responses (always present after DB save)
class HeroUpdate(SQLModel):
name: str | None = None
secret_name: str | None = None
age: int | None = NoneHero.model_validate(hero_create_instance)db_hero = Hero.model_validate(hero) # hero is HeroCreate
session.add(db_hero)
session.commit()
session.refresh(db_hero)
return db_hero # FastAPI serializes via HeroPublic response_modelmodel_dump(exclude_unset=True)sqlmodel_update()@app.patch("/heroes/{hero_id}", response_model=HeroPublic)
def update_hero(
*,
session: Session = Depends(get_session),
hero_id: int,
hero: HeroUpdate,
) -> HeroPublic:
db_hero = session.get(Hero, hero_id)
if not db_hero:
raise HTTPException(status_code=404, detail="Hero not found")
hero_data = hero.model_dump(exclude_unset=True)
db_hero.sqlmodel_update(hero_data)
session.add(db_hero)
session.commit()
session.refresh(db_hero)
return db_heroselect()from sqlmodel import select
with Session(engine) as session:
# All rows
heroes = session.exec(select(Hero)).all()
# Single result
hero = session.exec(select(Hero).where(Hero.name == "Deadpond")).first()
# By primary key (preferred for single-row lookup)
hero = session.get(Hero, hero_id)
# Filtering, ordering, pagination
statement = (
select(Hero)
.where(Hero.age >= 18)
.order_by(Hero.name)
.offset(offset)
.limit(limit)
)
heroes = session.exec(statement).all()Field(index=True)WHEREORDER BYJOINclass Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True) # indexed
age: int | None = Field(default=None, index=True) # indexed
secret_name: str # not indexedRelationshipField(foreign_key="table.column")Relationshipfrom sqlmodel import Relationship
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
heroes: list["Hero"] = Relationship(back_populates="team")
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
team_id: int | None = Field(default=None, foreign_key="team.id")
team: Team | None = Relationship(back_populates="heroes")back_populates"Hero"table=Trueclass HeroTeamLink(SQLModel, table=True):
hero_id: int | None = Field(default=None, foreign_key="hero.id", primary_key=True)
team_id: int | None = Field(default=None, foreign_key="team.id", primary_key=True)
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
teams: list["Team"] = Relationship(back_populates="heroes", link_model=HeroTeamLink)create_allapp/
├── models.py # all SQLModel table models
├── schemas.py # data models (HeroCreate, HeroPublic, etc.)
├── database.py # engine, get_session dependency
└── routers/
└── heroes.py # FastAPI routerdatabase.pycreate_all# main.py
from app import models # ensures registration before create_all
from app.database import engine, create_db_and_tables
@app.on_event("startup")
def on_startup() -> None:
create_db_and_tables()| Operation | Code |
|---|---|
| Create engine | |
| Create tables | |
| Open session | |
| Insert row | |
| Fetch by PK | |
| Query rows | |
| Update row | |
| Delete row | |
| Refresh from DB | |
| Convert input→table | |
| Partial update dict | |
references/relationships-and-queries.mdreferences/fastapi-patterns.md