Use when building Python web APIs, database-backed applications, or backend services. Covers FastAPI, SQLAlchemy (async), PostgreSQL, Alembic migrations, and performance patterns. Triggers on API endpoint creation, database models, query optimization, or migration planning. Extends python-development skill.
/plugin marketplace add btimothy-har/mycc-config/plugin install brian-claude-skills@brian-claudeThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Extends the python-development skill. Use PostgreSQL, FastAPI, and async SQLAlchemy.
Database
selectinload or joinedloadpool_pre_ping=Truesession.add_all() and insert().values([...]) for batch processingtext() queries when ORM adds unnecessary overheadMigrations
API Design
Performance
EXPLAIN ANALYZE before optimizing queriesSecurity
.format() in SQL; use bound parameters# pyproject.toml
[project]
dependencies = [
"fastapi",
"uvicorn[standard]",
"sqlalchemy[asyncio]",
"asyncpg",
"alembic",
"pydantic",
"pydantic-settings",
]
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from .database import get_session
from .models import User
from .schemas import UserCreate, UserResponse
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: initialize DB pool
yield
# Shutdown: cleanup
app = FastAPI(lifespan=lifespan)
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate, session: AsyncSession = Depends(get_session)):
db_user = User(**user.model_dump())
session.add(db_user)
await session.commit()
await session.refresh(db_user)
return db_user
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, session: AsyncSession = Depends(get_session)):
user = await session.get(User, user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
# database.py
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/dbname"
engine = create_async_engine(
DATABASE_URL,
pool_size=5,
max_overflow=10,
pool_pre_ping=True,
)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
class Base(DeclarativeBase):
pass
async def get_session() -> AsyncSession:
async with AsyncSessionLocal() as session:
yield session
# models.py
from datetime import datetime
from sqlalchemy import String, ForeignKey, Index
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
name: Mapped[str] = mapped_column(String(100))
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
posts: Mapped[list["Post"]] = relationship(back_populates="author", lazy="selectin")
class Post(Base):
__tablename__ = "posts"
__table_args__ = (
Index("ix_posts_author_created", "author_id", "created_at"),
)
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(200))
author_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
author: Mapped["User"] = relationship(back_populates="posts")
# schemas.py
from pydantic import BaseModel, EmailStr, ConfigDict
class UserCreate(BaseModel):
email: EmailStr
name: str
class UserResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
email: str
name: str
from sqlalchemy import select
from sqlalchemy.orm import selectinload, joinedload
# BAD: N+1 queries
users = await session.scalars(select(User))
for user in users:
print(user.posts) # Each access triggers a query
# GOOD: Eager load with selectinload (separate IN query)
stmt = select(User).options(selectinload(User.posts))
users = await session.scalars(stmt)
# GOOD: Eager load with joinedload (single JOIN query)
stmt = select(User).options(joinedload(User.posts))
users = await session.scalars(stmt)
# Initialize
alembic init alembic
# Generate migration
alembic revision --autogenerate -m "add users table"
# Apply migrations
alembic upgrade head
# Rollback one step
alembic downgrade -1
Configure alembic/env.py for async:
from sqlalchemy.ext.asyncio import async_engine_from_config
import asyncio
def run_async_migrations():
connectable = async_engine_from_config(config.get_section(config.config_ini_section))
async def do_run():
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
await connectable.dispose()
asyncio.run(do_run())
from sqlalchemy import text
from sqlalchemy.dialects.postgresql import JSONB, ARRAY
class Product(Base):
__tablename__ = "products"
id: Mapped[int] = mapped_column(primary_key=True)
metadata_: Mapped[dict] = mapped_column("metadata", JSONB, default={})
tags: Mapped[list[str]] = mapped_column(ARRAY(String), default=[])
# JSONB query
stmt = select(Product).where(Product.metadata_["category"].astext == "electronics")
# Array contains
stmt = select(Product).where(Product.tags.contains(["sale"]))
# Full-text search (use raw SQL for complex cases)
stmt = text("SELECT * FROM products WHERE to_tsvector(name) @@ to_tsquery(:query)")
result = await session.execute(stmt, {"query": "phone"})
from fastapi import Query
@app.get("/posts", response_model=list[PostResponse])
async def list_posts(
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
session: AsyncSession = Depends(get_session),
):
stmt = select(Post).offset(skip).limit(limit).order_by(Post.created_at.desc())
posts = await session.scalars(stmt)
return posts.all()
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000