craft-coder/backend-python/SKILL.md
Modern Python backend stack: FastAPI, SQLAlchemy 2.0, Pydantic v2, uv, ruff. Use when: building Python APIs, async services, or working with Python backend code. Triggers: "fastapi", "python backend", "sqlalchemy", "pydantic", "uv", "ruff", "python api", "async python".
npx skillsauth add timequity/plugins backend-pythonInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Live docs: Add
use context7to prompt for up-to-date FastAPI, SQLAlchemy, Pydantic documentation.
| Tool | Purpose | Why | |------|---------|-----| | uv | Package manager | 10-100x faster than pip | | ruff | Linter + formatter | Replaces black, isort, flake8 | | FastAPI | Web framework | Async, auto-docs, Pydantic | | SQLAlchemy 2.0 | ORM | Async support, type hints | | Pydantic v2 | Validation | 5-50x faster than v1 | | pytest | Testing | See testing.md |
# Initialize
uv init my-api
cd my-api
uv add fastapi sqlalchemy[asyncio] pydantic pydantic-settings
uv add --dev ruff pytest pytest-asyncio httpx
# pyproject.toml
[tool.ruff]
line-length = 100
select = ["E", "F", "I", "N", "UP", "B", "A", "C4", "SIM"]
[tool.ruff.format]
quote-style = "double"
[tool.pytest.ini_options]
asyncio_mode = "auto"
src/
├── main.py # FastAPI app
├── config.py # Settings (pydantic-settings)
├── db/
│ ├── __init__.py
│ ├── engine.py # Async engine + session
│ └── models.py # SQLAlchemy models
├── api/
│ ├── __init__.py
│ ├── deps.py # Dependencies (get_db, get_user)
│ └── routes/
│ ├── __init__.py
│ ├── auth.py
│ └── users.py
├── schemas/ # Pydantic models
│ ├── __init__.py
│ └── user.py
└── services/ # Business logic
└── user.py
tests/
├── conftest.py
└── test_users.py
from fastapi import FastAPI
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
await init_db()
yield
# Shutdown
await close_db()
app = FastAPI(lifespan=lifespan)
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/{user_id}", response_model=UserOut)
async def get_user(
user_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
user = await db.get(User, user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime
class UserCreate(BaseModel):
email: EmailStr
name: str = Field(min_length=1, max_length=100)
class UserOut(BaseModel):
id: int
email: str
name: str
created_at: datetime
model_config = {"from_attributes": True} # For ORM mode
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
database_url: str
secret_key: str
debug: bool = False
model_config = {"env_file": ".env"}
@lru_cache
def get_settings() -> Settings:
return Settings()
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, DeclarativeBase
engine = create_async_engine(
"postgresql+asyncpg://user:pass@localhost/db",
echo=False,
pool_size=5,
max_overflow=10,
)
AsyncSessionLocal = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
class Base(DeclarativeBase):
pass
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
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")
from typing import AsyncGenerator
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
from sqlalchemy import select
from sqlalchemy.orm import selectinload
# Get one
user = await db.get(User, user_id)
# Query with filter
stmt = select(User).where(User.email == email)
result = await db.execute(stmt)
user = result.scalar_one_or_none()
# Eager load relationships
stmt = select(User).options(selectinload(User.posts)).where(User.id == user_id)
# Pagination
stmt = select(User).offset(skip).limit(limit).order_by(User.created_at.desc())
result = await db.execute(stmt)
users = result.scalars().all()
from fastapi import HTTPException, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return JSONResponse(
status_code=422,
content={"error": {"code": "VALIDATION_ERROR", "details": exc.errors()}},
)
# Custom exceptions
class NotFoundError(Exception):
def __init__(self, resource: str, id: int):
self.resource = resource
self.id = id
@app.exception_handler(NotFoundError)
async def not_found_handler(request, exc):
return JSONResponse(
status_code=404,
content={"error": {"message": f"{exc.resource} {exc.id} not found"}},
)
| Don't | Do Instead |
|-------|------------|
| pip install | uv add |
| black + isort + flake8 | ruff |
| SQLAlchemy 1.x style | SQLAlchemy 2.0 with Mapped[] |
| Pydantic v1 | Pydantic v2 (model_config, not Config) |
| Sync database calls | Async with asyncpg/aiosqlite |
| Global DB session | Dependency injection |
| Business logic in routes | Services layer |
tools
Backup strategies, disaster recovery planning, and business continuity.
devops
Cloud cost management, rightsizing, and FinOps practices.
testing
CI/CD pipeline design with GitHub Actions, GitLab CI, and best practices.
development
Validate idea and create detailed PRD. Saves docs/PRD.md to project. Use when: user describes an app idea, wants to create something new. Triggers: "I want to build", "create app", "make website", "build MVP", "хочу создать", "сделать приложение".