sqlmodel-database
Expert in SQLModel ORM patterns for async PostgreSQL operations. Covers model definitions, relationships, async session management, queries with filtering/joins, and Neon PostgreSQL integration. Use for all database schema and query implementations.
When & Why to Use This Skill
This Claude skill provides expert guidance on implementing asynchronous PostgreSQL operations using SQLModel, the modern Python library that combines SQLAlchemy and Pydantic. It focuses on creating type-safe database schemas, managing async sessions, and optimizing queries for high-performance applications, with specific configurations for Neon Serverless PostgreSQL. By bridging the gap between data validation and ORM mapping, it ensures robust, scalable, and maintainable database layers in modern web frameworks like FastAPI.
Use Cases
- Designing type-safe database schemas: Define models that leverage Pydantic for validation and SQLAlchemy for database mapping, ensuring data integrity at the code level.
- Implementing high-performance async operations: Develop non-blocking database queries and session management patterns suitable for modern asynchronous Python services.
- Optimizing serverless database connections: Configure Neon PostgreSQL integrations with proper connection pooling, SSL settings, and 'pre-ping' checks to handle serverless scaling effectively.
- Managing complex data relationships: Set up one-to-many and many-to-many relationships with eager loading (selectinload) to prevent N+1 query performance issues.
- Building secure multi-tenant systems: Implement standardized filtering patterns (e.g., by user_id) and robust error handling for database constraints and integrity errors.
| name | sqlmodel-database |
|---|---|
| description | Expert in SQLModel ORM patterns for async PostgreSQL operations. Covers model definitions, relationships, async session management, queries with filtering/joins, and Neon PostgreSQL integration. Use for all database schema and query implementations. |
SQLModel Database - Async PostgreSQL with Neon
You are an expert in SQLModel, the Python library that combines SQLAlchemy and Pydantic for type-safe database operations. This skill covers async patterns for PostgreSQL, specifically with Neon Serverless PostgreSQL.
Core Philosophy
SQLModel = Pydantic Models + SQLAlchemy ORM
- Type safety: Full Python type hints and validation
- Async-first: All operations use
async/await - Pydantic integration: Automatic validation and serialization
- SQLAlchemy foundation: Powerful query capabilities
- Neon optimized: Connection pooling for serverless environments
When to Use This Skill
✅ Use this skill for:
- Defining database models with proper types and constraints
- Creating table relationships (one-to-many, many-to-many)
- Writing async database queries with filtering and joins
- Managing async database sessions
- Setting up Neon PostgreSQL connections
- Handling migrations and schema changes
- Optimizing queries with relationship loading
❌ Don't use for:
- Basic SQL syntax (SELECT, WHERE) - you know this
- General database concepts - fundamental knowledge
- Python async/await basics - covered in training
Fundamental Patterns
1. Model Definition
from sqlmodel import SQLModel, Field
from typing import Optional
from datetime import datetime
class Todo(SQLModel, table=True):
__tablename__ = "todos" # Optional: explicit table name
# Primary key
id: str = Field(primary_key=True)
# Required fields
title: str = Field(index=True) # Add index for frequent queries
user_id: str = Field(foreign_key="users.id", index=True)
# Optional fields with defaults
completed: bool = Field(default=False)
description: Optional[str] = Field(default=None)
# Timestamps
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: Optional[datetime] = Field(default=None)
Key Concepts:
table=Truemarks this as a database tableField()provides database-specific configuration- Type hints are required and enforced
- Optional fields use
Optional[Type]orType | None - Foreign keys reference "table.column"
2. Async Session Management
from sqlmodel import create_engine
from sqlmodel.ext.asyncio.session import AsyncSession, AsyncEngine
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker
# Create async engine
DATABASE_URL = "postgresql+asyncpg://user:pass@host/db"
engine = create_async_engine(DATABASE_URL, echo=True, future=True)
# Create async session factory
async_session = sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False
)
# Dependency for FastAPI
async def get_session() -> AsyncSession:
async with async_session() as session:
yield session
Key Concepts:
- Use
asyncpgdriver for PostgreSQL (postgresql+asyncpg://) expire_on_commit=Falseprevents lazy loading issues- Always use context managers (
async with) - Yield session in FastAPI dependencies
3. Basic Queries
from sqlmodel import select
# SELECT with filtering
async def get_user_todos(session: AsyncSession, user_id: str):
statement = select(Todo).where(Todo.user_id == user_id)
result = await session.exec(statement)
return result.all()
# SELECT single record
async def get_todo_by_id(session: AsyncSession, todo_id: str):
statement = select(Todo).where(Todo.id == todo_id)
result = await session.exec(statement)
return result.first() # Returns None if not found
# INSERT
async def create_todo(session: AsyncSession, todo: Todo):
session.add(todo)
await session.commit()
await session.refresh(todo) # Get DB-generated values
return todo
# UPDATE
async def update_todo(session: AsyncSession, todo: Todo):
session.add(todo) # Works for updates too
await session.commit()
await session.refresh(todo)
return todo
# DELETE
async def delete_todo(session: AsyncSession, todo: Todo):
await session.delete(todo)
await session.commit()
Key Concepts:
- Build queries with
select(Model).where(...) - Execute with
await session.exec(statement) .all()returns list,.first()returns single/None- Always
await session.commit()after changes await session.refresh()to get updated values
4. Relationships
from sqlmodel import Relationship
from typing import List
class User(SQLModel, table=True):
__tablename__ = "users"
id: str = Field(primary_key=True)
email: str = Field(unique=True, index=True)
# One-to-many: one user has many todos
todos: List["Todo"] = Relationship(back_populates="user")
class Todo(SQLModel, table=True):
__tablename__ = "todos"
id: str = Field(primary_key=True)
title: str
user_id: str = Field(foreign_key="users.id")
# Many-to-one: many todos belong to one user
user: Optional[User] = Relationship(back_populates="todos")
Key Concepts:
Relationship()defines ORM relationships (not database constraints)back_populatesmust match field name on other model- Forward reference with quotes:
List["Todo"] - Foreign key defined separately with
Field(foreign_key=...)
5. Relationship Loading
from sqlalchemy.orm import selectinload
# Eager load relationships (prevents N+1 queries)
async def get_user_with_todos(session: AsyncSession, user_id: str):
statement = (
select(User)
.where(User.id == user_id)
.options(selectinload(User.todos)) # Load todos eagerly
)
result = await session.exec(statement)
return result.first()
# Access loaded relationships
user = await get_user_with_todos(session, "user-123")
for todo in user.todos: # No additional query needed
print(todo.title)
Key Concepts:
selectinload()loads relationships in single query- Prevents N+1 query problem
- Use for relationships you know you'll access
- Alternative:
joinedload()for left joins
Integration with FastAPI
from fastapi import Depends, HTTPException
from sqlmodel.ext.asyncio.session import AsyncSession
@app.post("/todos", response_model=TodoPublic)
async def create_todo(
todo: TodoCreate,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_user)
):
# Create model instance
db_todo = Todo(
id=str(uuid4()),
title=todo.title,
user_id=current_user.id,
completed=False
)
# Save to database
session.add(db_todo)
await session.commit()
await session.refresh(db_todo)
return db_todo
@app.get("/todos", response_model=List[TodoPublic])
async def get_todos(
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_user)
):
statement = select(Todo).where(Todo.user_id == current_user.id)
result = await session.exec(statement)
return result.all()
Key Concepts:
- Inject
AsyncSessionviaDepends(get_session) - Always filter by
user_idfor multi-tenant security - Use Pydantic models for request/response (TodoCreate, TodoPublic)
- SQLModel instances can be returned directly (auto-serialization)
Neon PostgreSQL Specifics
Connection String Format
# Neon connection string
DATABASE_URL = "postgresql+asyncpg://user:password@host.neon.tech/dbname?sslmode=require"
# From environment
import os
DATABASE_URL = os.environ.get("DATABASE_URL")
# Engine configuration for Neon
engine = create_async_engine(
DATABASE_URL,
echo=False, # Set True for SQL logging
pool_pre_ping=True, # Verify connections before using
pool_size=5, # Limit connection pool
max_overflow=10
)
Neon Considerations:
- Always use SSL:
?sslmode=require - Connection pooling limits for serverless
- Use
pool_pre_ping=Trueto handle stale connections - Neon auto-scales, but respect connection limits
Schema Creation
from sqlmodel import SQLModel
# Create all tables
async def init_db():
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
# Drop all tables (development only!)
async def drop_db():
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.drop_all)
Key Concepts:
create_all()creates tables if they don't exist- Use for initial setup or simple projects
- For production, use Alembic migrations (see migrations.md)
Common Query Patterns
Filtering
# Multiple conditions
statement = select(Todo).where(
Todo.user_id == user_id,
Todo.completed == False
)
# OR conditions
from sqlalchemy import or_
statement = select(Todo).where(
or_(Todo.completed == True, Todo.user_id == user_id)
)
# LIKE for partial matching
statement = select(Todo).where(Todo.title.like("%search%"))
Ordering
statement = select(Todo).order_by(Todo.created_at.desc())
statement = select(Todo).order_by(Todo.completed, Todo.title)
Pagination
page = 1
page_size = 20
offset = (page - 1) * page_size
statement = (
select(Todo)
.where(Todo.user_id == user_id)
.offset(offset)
.limit(page_size)
)
Counting
from sqlalchemy import func
statement = select(func.count(Todo.id)).where(Todo.user_id == user_id)
result = await session.exec(statement)
count = result.one()
When to Query Context7
Use the using-context7 skill to query for:
✅ "SQLModel async session management best practices"
✅ "SQLModel relationship loading with selectinload"
✅ "SQLModel Alembic migrations setup"
✅ "Neon PostgreSQL connection string format for SQLModel"
✅ "SQLModel with FastAPI dependency injection patterns"
Don't query for:
❌ Basic SQL syntax (SELECT, WHERE, JOIN)
❌ Python type hints
❌ General database concepts
❌ Python async/await basics
Error Handling
from sqlalchemy.exc import IntegrityError, NoResultFound
try:
session.add(new_todo)
await session.commit()
except IntegrityError as e:
await session.rollback()
raise HTTPException(status_code=400, detail="Duplicate or invalid data")
except Exception as e:
await session.rollback()
raise HTTPException(status_code=500, detail=str(e))
Key Concepts:
- Always
await session.rollback()on errors IntegrityErrorfor constraint violations (unique, foreign key)- Handle in FastAPI with appropriate HTTP status codes
Performance Tips
Use indexes on frequently queried columns:
user_id: str = Field(foreign_key="users.id", index=True)Eager load relationships to prevent N+1:
.options(selectinload(User.todos))Select specific columns when not needing full objects:
statement = select(Todo.id, Todo.title).where(...)Use pagination for large result sets:
.offset(offset).limit(limit)Connection pooling for Neon:
engine = create_async_engine(url, pool_size=5, max_overflow=10)
Related Skill Files
model-patterns.md- Model definitions and Field configurationsquery-patterns.md- Advanced queries, joins, aggregationssession-management.md- Session lifecycle and context managersneon-specific.md- Neon PostgreSQL connection and poolingmigrations.md- Alembic setup and schema migrationsreference.md- Quick reference for common patternsexamples.md- Real Phase 2 database scenarios
Remember
- Always use async -
async def,await session.exec(),await session.commit() - Type everything - SQLModel enforces types for safety
- Filter by user_id - Multi-tenant security is critical
- Use relationships wisely - Eager load to prevent N+1 queries
- Handle errors - Rollback on exceptions, return proper HTTP codes
- Query Context7 - For framework-specific patterns, not SQL basics
This skill provides the foundation for all database operations in Phase 2. Combine it with fastapi-async-patterns for complete CRUD implementations.