sqlmodel

SQLModel - Pydantic + SQLAlchemy in One

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "sqlmodel" with this command: npx skills add shmlaiq/task-managment-api/shmlaiq-task-managment-api-sqlmodel

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:

Source Gather

Codebase Existing models, database setup, relationship patterns

Conversation Entity requirements, relationships needed, validation rules

Skill References Patterns from references/ directory

User Guidelines Naming conventions, project structure preferences

Clarifications

Required (ask if not clear)

  • Database? PostgreSQL / SQLite / MySQL

  • Async needed? Yes (asyncpg/aiosqlite) / No (sync)

  • Relationships? One-to-Many / Many-to-Many / None

Optional (ask if relevant)

  • Migrations? Alembic / Manual / None

  • Framework? FastAPI / Standalone Python

Official Documentation

Resource URL Use For

SQLModel Docs https://sqlmodel.tiangolo.com Official reference

SQLAlchemy Docs https://docs.sqlalchemy.org Advanced ORM features

Pydantic Docs https://docs.pydantic.dev Validation patterns

Alembic Docs https://alembic.sqlalchemy.org Database 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

Need Solution

Install uv add sqlmodel

Create table class Hero(SQLModel, table=True)

Primary key Field(default=None, primary_key=True)

Foreign key Field(foreign_key="table.id")

Index Field(index=True)

Unique Field(unique=True)

Create tables SQLModel.metadata.create_all(engine)

Session with Session(engine) as session:

Select all session.exec(select(Model)).all()

Get by ID session.get(Model, id)

Add session.add(obj); session.commit()

Delete session.delete(obj); session.commit()

Refresh session.refresh(obj)

Common Mistakes

Mistake Why It's Wrong Fix

Missing table=True

Model won't create DB table Add table=True to DB models

id: int without None

Can't create new records Use id: int | None = Field(default=None, ...)

Forgetting session.commit()

Changes not persisted Always commit after add/update/delete

Not using model_validate()

Type conversion issues Use Hero.model_validate(hero_create)

Missing session.refresh()

Stale data after commit Refresh to get DB-generated values

Circular relationship imports ImportError Use 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

gmail-reply

No summary provided by upstream source.

Repository SourceNeeds Review
General

pytest

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi

No summary provided by upstream source.

Repository SourceNeeds Review