Agent Skill
2/7/2026

sqlmodel

SQLModel - Pydantic + SQLAlchemy combined. This skill should be used when building FastAPI apps with database, creating ORM models, data validation with database persistence, or any Python project needing both validation and ORM. Triggers on "create database model", "add SQLModel", "database with FastAPI", "ORM model", or any SQLModel-related development.

S
shmlaiq
0GitHub Stars
2Views
npx skills add shmlaiq/Task-Managment-API

SKILL.md

Namesqlmodel
DescriptionSQLModel - Pydantic + SQLAlchemy combined. This skill should be used when building FastAPI apps with database, creating ORM models, data validation with database persistence, or any Python project needing both validation and ORM. Triggers on "create database model", "add SQLModel", "database with FastAPI", "ORM model", or any SQLModel-related development.

name: sqlmodel description: SQLModel - Pydantic + SQLAlchemy combined. This skill should be used when building FastAPI apps with database, creating ORM models, data validation with database persistence, or any Python project needing both validation and ORM. Triggers on "create database model", "add SQLModel", "database with FastAPI", "ORM model", or any SQLModel-related development.

SQLModel - Pydantic + SQLAlchemy in One

One model for both validation AND database. Created by Sebastián Ramírez (FastAPI creator).

Before Implementation

Gather context to ensure successful implementation:

SourceGather
CodebaseExisting models, database setup, relationship patterns
ConversationEntity requirements, relationships needed, validation rules
Skill ReferencesPatterns from references/ directory
User GuidelinesNaming conventions, project structure preferences

Clarifications

Required (ask if not clear)

  1. Database? PostgreSQL / SQLite / MySQL
  2. Async needed? Yes (asyncpg/aiosqlite) / No (sync)
  3. Relationships? One-to-Many / Many-to-Many / None

Optional (ask if relevant)

  1. Migrations? Alembic / Manual / None
  2. Framework? FastAPI / Standalone Python

Official Documentation

ResourceURLUse For
SQLModel Docshttps://sqlmodel.tiangolo.comOfficial reference
SQLAlchemy Docshttps://docs.sqlalchemy.orgAdvanced ORM features
Pydantic Docshttps://docs.pydantic.devValidation patterns
Alembic Docshttps://alembic.sqlalchemy.orgDatabase migrations

Version Note: This skill follows SQLModel 0.0.16+ and Pydantic v2 patterns.

TDD Workflow (Red-Green-Refactor)

ALWAYS follow TDD when building with SQLModel:

The Cycle

🔴 RED    → Write a failing test for model/endpoint
🟢 GREEN  → Create minimal model/code to pass
🔄 REFACTOR → Improve code, keep tests green

TDD Example: Hero Model

# Step 1: 🔴 RED - Write test first
def test_create_hero(client):
    response = client.post("/heroes/", json={
        "name": "Spider-Boy",
        "secret_name": "Pedro Parqueador"
    })
    assert response.status_code == 201
    assert response.json()["name"] == "Spider-Boy"

# Step 2: 🟢 GREEN - Create model and endpoint
class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str

# Step 3: 🔄 REFACTOR - Add indexes, relationships

Quick Start

# Initialize project
uv init my-app && cd my-app

# Install SQLModel
uv add sqlmodel

# For FastAPI integration
uv add sqlmodel fastapi "uvicorn[standard]"

# For async support
uv add sqlmodel aiosqlite  # SQLite async
uv add sqlmodel asyncpg    # PostgreSQL async

Core Concept: One Model, Multiple Uses

from sqlmodel import Field, SQLModel

# Base model (shared fields)
class HeroBase(SQLModel):
    name: str
    secret_name: str
    age: int | None = None

# Database model (table=True)
class Hero(HeroBase, table=True):
    id: int | None = Field(default=None, primary_key=True)

# Create schema (request body)
class HeroCreate(HeroBase):
    pass

# Read schema (response)
class HeroRead(HeroBase):
    id: int

# Update schema (partial updates)
class HeroUpdate(SQLModel):
    name: str | None = None
    secret_name: str | None = None
    age: int | None = None

Basic CRUD Operations

from sqlmodel import Session, select, create_engine

DATABASE_URL = "sqlite:///database.db"
engine = create_engine(DATABASE_URL)

# CREATE
def 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_hero

# READ (single)
def get_hero(session: Session, hero_id: int) -> Hero | None:
    return session.get(Hero, hero_id)

# READ (list)
def get_heroes(session: Session, skip: int = 0, limit: int = 100) -> list[Hero]:
    statement = select(Hero).offset(skip).limit(limit)
    return session.exec(statement).all()

# UPDATE
def update_hero(session: Session, hero_id: int, hero_update: HeroUpdate) -> Hero | None:
    db_hero = session.get(Hero, hero_id)
    if db_hero:
        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_hero

# DELETE
def delete_hero(session: Session, hero_id: int) -> bool:
    hero = session.get(Hero, hero_id)
    if hero:
        session.delete(hero)
        session.commit()
        return True
    return False

FastAPI Integration

from fastapi import FastAPI, Depends, HTTPException
from sqlmodel import Session, SQLModel, create_engine

app = FastAPI()

def get_session():
    with Session(engine) as session:
        yield session

@app.on_event("startup")
def on_startup():
    SQLModel.metadata.create_all(engine)

@app.post("/heroes/", response_model=HeroRead, status_code=201)
def create_hero(hero: HeroCreate, session: Session = Depends(get_session)):
    db_hero = Hero.model_validate(hero)
    session.add(db_hero)
    session.commit()
    session.refresh(db_hero)
    return db_hero

@app.get("/heroes/{hero_id}", response_model=HeroRead)
def read_hero(hero_id: int, session: Session = Depends(get_session)):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero

Workflow Selection

Starting with SQLModel? → See references/basics.md

Building FastAPI + SQLModel? → See references/fastapi-integration.md

Need relationships (1:N, N:N)? → See references/relationships.md

Database migrations? → See references/migrations.md

Async database? → See references/async.md

Field Configuration

from sqlmodel import Field

class Hero(SQLModel, table=True):
    # Primary key
    id: int | None = Field(default=None, primary_key=True)

    # Required with index
    name: str = Field(index=True)

    # Optional with default
    age: int | None = Field(default=None, index=True)

    # Unique constraint
    email: str = Field(unique=True)

    # Foreign key
    team_id: int | None = Field(default=None, foreign_key="team.id")

    # With validation
    power_level: int = Field(ge=0, le=100)

    # Max length (for VARCHAR)
    description: str | None = Field(default=None, max_length=500)

Query Examples

from sqlmodel import select, or_, and_, col

# Basic select
statement = select(Hero)
heroes = session.exec(statement).all()

# Where clause
statement = select(Hero).where(Hero.name == "Spider-Boy")
hero = session.exec(statement).first()

# Multiple conditions (AND)
statement = select(Hero).where(Hero.age >= 18, Hero.age <= 65)

# OR conditions
statement = select(Hero).where(or_(Hero.name == "Spider-Boy", Hero.name == "Deadpond"))

# LIKE query
statement = select(Hero).where(col(Hero.name).contains("Spider"))

# Order by
statement = select(Hero).order_by(Hero.name)
statement = select(Hero).order_by(col(Hero.age).desc())

# Limit and offset
statement = select(Hero).offset(10).limit(5)

# Count
from sqlmodel import func
statement = select(func.count()).select_from(Hero)
count = session.exec(statement).one()

Testing with SQLModel

# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session, SQLModel, create_engine
from sqlmodel.pool import StaticPool

from app.main import app, get_session

@pytest.fixture(name="session")
def session_fixture():
    engine = create_engine(
        "sqlite://",  # In-memory database
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    SQLModel.metadata.create_all(engine)
    with Session(engine) as session:
        yield session

@pytest.fixture(name="client")
def client_fixture(session: Session):
    def get_session_override():
        return session

    app.dependency_overrides[get_session] = get_session_override
    client = TestClient(app)
    yield client
    app.dependency_overrides.clear()
# tests/test_heroes.py
def test_create_hero(client):
    response = client.post("/heroes/", json={
        "name": "Spider-Boy",
        "secret_name": "Pedro Parqueador"
    })
    assert response.status_code == 201
    data = response.json()
    assert data["name"] == "Spider-Boy"
    assert "id" in data

def test_read_hero(client):
    # Create first
    response = client.post("/heroes/", json={
        "name": "Deadpond",
        "secret_name": "Dive Wilson"
    })
    hero_id = response.json()["id"]

    # Then read
    response = client.get(f"/heroes/{hero_id}")
    assert response.status_code == 200
    assert response.json()["name"] == "Deadpond"

def test_read_hero_not_found(client):
    response = client.get("/heroes/999")
    assert response.status_code == 404

Run Tests

uv run pytest tests/ -v
uv run pytest tests/ --cov=app --cov-report=term-missing

Quick Reference

NeedSolution
Installuv add sqlmodel
Create tableclass Hero(SQLModel, table=True)
Primary keyField(default=None, primary_key=True)
Foreign keyField(foreign_key="table.id")
IndexField(index=True)
UniqueField(unique=True)
Create tablesSQLModel.metadata.create_all(engine)
Sessionwith Session(engine) as session:
Select allsession.exec(select(Model)).all()
Get by IDsession.get(Model, id)
Addsession.add(obj); session.commit()
Deletesession.delete(obj); session.commit()
Refreshsession.refresh(obj)

Common Mistakes

MistakeWhy It's WrongFix
Missing table=TrueModel won't create DB tableAdd table=True to DB models
id: int without NoneCan't create new recordsUse id: int | None = Field(default=None, ...)
Forgetting session.commit()Changes not persistedAlways commit after add/update/delete
Not using model_validate()Type conversion issuesUse Hero.model_validate(hero_create)
Missing session.refresh()Stale data after commitRefresh to get DB-generated values
Circular relationship importsImportErrorUse TYPE_CHECKING and string annotations

Before Delivery Checklist

Model Quality

  • All DB models have table=True
  • Primary keys use Field(default=None, primary_key=True)
  • Separate schemas: Base, Create, Read, Update
  • Indexes on frequently queried fields

Database Operations

  • All operations use session.commit()
  • New objects refreshed after commit
  • Proper error handling for not found
  • Session dependency yields and closes

Relationships

  • Foreign keys properly defined
  • Relationship() configured both sides
  • Eager loading where needed (selectinload)

Testing

  • In-memory SQLite for tests
  • Dependency override for test session
  • Tests pass: uv run pytest
Skills Info
Original Name:sqlmodelAuthor:shmlaiq