python-backend

SKILL.md

Python Backend

Expert-level Python backend patterns for FastAPI, Django, Flask, and SQLAlchemy. Use this skill for building REST APIs, database patterns, dependency injection, and backend architecture.

When to Use This Skill

  • Building REST APIs with FastAPI or Django
  • Working with SQLAlchemy or Django ORM
  • Implementing dependency injection
  • Designing API endpoints and versioning
  • Handling authentication and authorization
  • Database optimization patterns

FastAPI Patterns

Pydantic Models

from pydantic import BaseModel, EmailStr, Field, field_validator

class UserCreate(BaseModel):
    email: EmailStr
    name: str = Field(..., min_length=2, max_length=100)
    password: str = Field(..., min_length=8)

    @field_validator('name')
    @classmethod
    def name_must_not_be_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError('Name cannot be empty')
        return v.strip()

    model_config = {'str_strip_whitespace': True}

Dependency Injection

from typing import Annotated
from fastapi import Depends, HTTPException

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: AsyncSession = Depends(get_db)
) -> User:
    user = await verify_token(token, db)
    if user is None:
        raise HTTPException(status_code=401, detail="Invalid token")
    return user

CurrentUser = Annotated[User, Depends(get_current_user)]
DB = Annotated[AsyncSession, Depends(get_db)]

@app.get("/me")
async def get_me(user: CurrentUser, db: DB):
    return user

Background Tasks

from fastapi import BackgroundTasks

@app.post("/users/")
async def create_user(
    user: UserCreate,
    background_tasks: BackgroundTasks,
    db: AsyncSession = Depends(get_db)
):
    db_user = await crud.create_user(db, user)
    background_tasks.add_task(send_welcome_email, user.email)
    return db_user

Exception Handling

from fastapi import HTTPException
from fastapi.responses import JSONResponse

class AppException(Exception):
    def __init__(self, code: str, message: str, status_code: int = 400):
        self.code = code
        self.message = message
        self.status_code = status_code

@app.exception_handler(AppException)
async def app_exception_handler(request, exc: AppException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"code": exc.code, "message": exc.message}
    )

Lifespan Events

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup
    await database.connect()
    yield
    # Shutdown
    await database.disconnect()

app = FastAPI(lifespan=lifespan)

SQLAlchemy 2.0 Patterns

Modern Declarative Syntax

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy import ForeignKey

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(unique=True, index=True)
    name: Mapped[str]
    is_active: Mapped[bool] = mapped_column(default=True)

    posts: Mapped[list["Post"]] = relationship(back_populates="author")

Async Queries

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

async def get_user(db: AsyncSession, user_id: int) -> User | None:
    stmt = select(User).where(User.id == user_id)
    result = await db.execute(stmt)
    return result.scalar_one_or_none()

Eager Loading

from sqlalchemy.orm import selectinload, joinedload

# Prevent N+1 queries
stmt = select(User).options(
    selectinload(User.posts),  # Separate query
    joinedload(User.profile)   # JOIN
)

Pagination

from sqlalchemy import func, select

async def get_users_paginated(
    db: AsyncSession,
    page: int = 1,
    per_page: int = 20
) -> tuple[list[User], int]:
    # Count total
    count_stmt = select(func.count()).select_from(User)
    total = await db.scalar(count_stmt)

    # Get page
    stmt = select(User).offset((page - 1) * per_page).limit(per_page)
    result = await db.execute(stmt)
    users = result.scalars().all()

    return users, total

Django Patterns

Prevent N+1 Queries

# BAD - N+1 queries
users = User.objects.all()
for user in users:
    print(user.profile.bio)  # 1 query per user!

# GOOD - select_related for FK
users = User.objects.select_related('profile').all()

# GOOD - prefetch_related for M2M/reverse FK
posts = Post.objects.prefetch_related('tags', 'comments').all()

Atomic Updates

from django.db.models import F

# BAD - Race condition
article = Article.objects.get(pk=1)
article.views += 1
article.save()

# GOOD - Atomic update
Article.objects.filter(pk=1).update(views=F('views') + 1)

Complex Queries

from django.db.models import Q

# OR query
users = User.objects.filter(
    Q(is_staff=True) | Q(is_superuser=True)
)

# Combined conditions
users = User.objects.filter(
    Q(email__endswith='@company.com') &
    (Q(is_active=True) | Q(is_staff=True))
)

Efficient Checks

# BAD - Loads all objects
if len(User.objects.filter(email=email)) > 0:
    ...

# GOOD - Efficient existence check
if User.objects.filter(email=email).exists():
    ...

Bulk Operations

# BAD - N queries
for user_data in users_data:
    User.objects.create(**user_data)

# GOOD - 1 query
User.objects.bulk_create([
    User(**data) for data in users_data
])

Transactions

from django.db import transaction

@transaction.atomic
def transfer_funds(from_account, to_account, amount):
    from_account.balance = F('balance') - amount
    from_account.save()
    to_account.balance = F('balance') + amount
    to_account.save()

API Design

RESTful Conventions

Method Endpoint Purpose Response
GET /users List all 200 + array
GET /users/:id Get one 200 / 404
POST /users Create 201 + created
PUT /users/:id Replace 200 / 404
PATCH /users/:id Update 200 / 404
DELETE /users/:id Delete 204 / 404

Nested Resources

GET    /users/:userId/orders          # User's orders
POST   /users/:userId/orders          # Create order for user
GET    /users/:userId/orders/:orderId # Specific order

Naming Conventions

Type Convention Example
Resources Plural nouns /users, /orders
Actions Verbs (rare) /auth/login, /reports/generate
Query params snake_case ?page_size=20
Request/Response camelCase { "firstName": "John" }

Versioning Strategies

Strategy Example When to Use
URL Path /v1/users Most common, clear
Header Accept: application/vnd.api+json;version=1 Clean URLs
Query /users?version=1 Simple but messy

Recommended: URL path (/api/v1/...)

Response Format

Success

{
  "data": { "id": "123", "name": "John" },
  "meta": { "timestamp": "2025-01-01T00:00:00Z" }
}

List with Pagination

{
  "data": [{ "id": "1" }, { "id": "2" }],
  "meta": {
    "page": 1,
    "pageSize": 20,
    "total": 100,
    "totalPages": 5
  }
}

Error

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "fields": { "email": "Invalid format" }
  }
}

Status Codes

Code Meaning When to Use
200 OK Successful GET/PUT/PATCH
201 Created Successful POST
204 No Content Successful DELETE
400 Bad Request Invalid input
401 Unauthorized No/invalid auth
403 Forbidden No permission
404 Not Found Resource missing
409 Conflict Duplicate/state conflict
422 Unprocessable Validation failed
429 Too Many Rate limited
500 Server Error Unexpected error

Pagination Patterns

Offset-based (Simple)

GET /users?page=2&page_size=20

Cursor-based (Scalable)

GET /users?cursor=eyJpZCI6MTAwfQ&limit=20
Pattern Pros Cons
Offset Simple, jump to page Slow on large datasets
Cursor Consistent, fast No page jumping

Filtering & Sorting

# Filter
GET /users?status=active&role=admin

# Sort
GET /users?sort=created_at:desc,name:asc

# Search
GET /users?q=john

# Combined
GET /users?status=active&sort=name:asc&page=1&page_size=20

API Documentation (OpenAPI)

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: page
          in: query
          schema: { type: integer, default: 1 }
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserList'

Result Pattern (No Exceptions in Core)

from dataclasses import dataclass
from typing import Generic, TypeVar

T = TypeVar('T')
E = TypeVar('E')

@dataclass
class Ok(Generic[T]):
    value: T

@dataclass
class Err(Generic[E]):
    error: E

Result = Ok[T] | Err[E]

async def get_user(user_id: int) -> Result[User, str]:
    user = await db.get(User, user_id)
    if user is None:
        return Err("User not found")
    return Ok(user)

# Usage
match await get_user(123):
    case Ok(user):
        print(f"Found: {user.name}")
    case Err(error):
        print(f"Error: {error}")

Best Practices

Do's

  • Use plural nouns for resources
  • Return created/updated resource
  • Include pagination metadata
  • Document with OpenAPI
  • Version from day one
  • Use Pydantic for validation
  • Prefer async for I/O operations

Don'ts

  • Use verbs in resource names
  • Return different formats per endpoint
  • Expose internal IDs/structures
  • Ignore backward compatibility
  • Skip error code standards
  • Use bare except clauses

References

Weekly Installs
5
GitHub Stars
2
First Seen
11 days ago
Installed on
opencode5
claude-code5
github-copilot5
junie5
windsurf5
codex5