Claude Code Plugins

Community-maintained marketplace

Feedback

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name python-backend
description 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.

Python Backend Development

Extends the python-development skill. Use PostgreSQL, FastAPI, and async SQLAlchemy.

Principles

Database

  • Async by default - Use async SQLAlchemy and async endpoints for all I/O
  • Prevent N+1 queries - Always eager load relationships with selectinload or joinedload
  • PostgreSQL features - Leverage JSONB, arrays, and full-text search when beneficial
  • Connection pooling - Configure pool_size and max_overflow; use pool_pre_ping=True
  • Index intentionally - Create indexes for filtered/sorted columns; use composite indexes for multi-column queries
  • Bulk operations - Use session.add_all() and insert().values([...]) for batch processing
  • Raw SQL when needed - Drop to text() queries when ORM adds unnecessary overhead

Migrations

  • Migrations are code - Use Alembic for all schema changes, never manual DDL
  • Backwards-compatible - Design migrations that work with both old and new application code
  • Rollback strategy - Every migration should have a working downgrade path
  • Separate data migrations - Handle data transformations in separate migrations from schema changes

API Design

  • RESTful conventions - Proper HTTP methods, status codes, and resource naming
  • Consistent errors - Use structured error responses with error codes and messages
  • Paginate lists - All list endpoints return paginated results with limit/offset or cursors
  • Validate inputs - Use Pydantic models for request validation; fail fast on bad data
  • Document with OpenAPI - Leverage FastAPI's automatic OpenAPI generation; add descriptions

Performance

  • Profile first - Use EXPLAIN ANALYZE before optimizing queries
  • Cache strategically - Cache expensive queries; invalidate on writes
  • Minimize round-trips - Batch database operations; avoid loops with queries inside

Security

  • Parameterized queries only - Never use f-strings or .format() in SQL; use bound parameters
  • Validate and sanitize inputs - Trust nothing from the client; validate types, lengths, and formats
  • Hash passwords properly - Use bcrypt or argon2; never store plaintext or weak hashes
  • Authenticate then authorize - Verify identity first, then check permissions for the resource
  • Rate limit endpoints - Protect auth endpoints and expensive operations from abuse

Project Dependencies

# pyproject.toml
[project]
dependencies = [
    "fastapi",
    "uvicorn[standard]",
    "sqlalchemy[asyncio]",
    "asyncpg",
    "alembic",
    "pydantic",
    "pydantic-settings",
]

FastAPI Application

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

Async SQLAlchemy Setup

# 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

Model Patterns

# 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")

Pydantic Schemas

# 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

N+1 Query Prevention

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)

Alembic Migrations

# 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())

PostgreSQL Features

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"})

Pagination Pattern

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()

Running the Server

uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000