Claude Code Plugins

Community-maintained marketplace

Feedback

Python coding conventions, type hints, and FastAPI patterns

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
description Python coding conventions, type hints, and FastAPI patterns

Python Skill

Use this skill when working with Python code.

Required Practices

These rules must always be followed:

No Mutable Default Arguments

# Bad - mutable default is shared between calls
def add_item(item, items=[]):
    items.append(item)
    return items

# Good - use None and create new list
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

No Wildcard Imports

# Bad - pollutes namespace, unclear origins
from os import *
from utils import *

# Good - explicit imports
from os import path, getcwd
from utils import validate_email, format_date

Use logging, Not print

# Bad - print statements for logging
print(f"Processing user {user_id}")
print(f"Error: {e}")

# Good - use logging module
import logging

logger = logging.getLogger(__name__)
logger.info("Processing user %s", user_id)
logger.exception("Failed to process user")

No global Keyword

# Bad - global state is hard to test and reason about
counter = 0

def increment():
    global counter
    counter += 1

# Good - use classes or pass state explicitly
class Counter:
    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1

# Or use a closure/module-level singleton if truly needed

Always Use Type Hints for Function Signatures

# Bad - no type information
def get_user(user_id):
    ...

def process_items(items):
    ...

# Good - full type hints
def get_user(user_id: int) -> User | None:
    ...

def process_items(items: list[Item]) -> ProcessingResult:
    ...

# Also good - complex types with TypeAlias
from typing import TypeAlias

UserId: TypeAlias = int
UserMap: TypeAlias = dict[UserId, User]

def get_users(ids: list[UserId]) -> UserMap:
    ...

Type Hint Patterns

Basic Types

def greet(name: str) -> str:
    return f"Hello, {name}"

def calculate(x: int, y: float) -> float:
    return x * y

def is_valid(value: str) -> bool:
    return len(value) > 0

Optional and Union

from typing import Optional

# Python 3.10+ union syntax
def find_user(user_id: int) -> User | None:
    ...

# Optional is equivalent to X | None
def find_user(user_id: int) -> Optional[User]:
    ...

Collections

# Python 3.9+ built-in generics
def process(items: list[str]) -> dict[str, int]:
    ...

def get_unique(values: set[int]) -> frozenset[int]:
    ...

Callables

from typing import Callable

def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

# With default
def process(callback: Callable[[str], None] | None = None) -> None:
    ...

Error Handling

Use Specific Exceptions

# Bad - bare except
try:
    process()
except:
    pass

# Bad - catching Exception broadly
try:
    process()
except Exception:
    logger.error("Something went wrong")

# Good - catch specific exceptions
try:
    result = parse_json(data)
except json.JSONDecodeError as e:
    logger.warning("Invalid JSON: %s", e)
    raise ValidationError("Invalid input format") from e

Custom Exceptions

class AppError(Exception):
    """Base exception for application errors."""
    pass

class NotFoundError(AppError):
    """Resource not found."""
    pass

class ValidationError(AppError):
    """Input validation failed."""
    def __init__(self, message: str, field: str | None = None):
        super().__init__(message)
        self.field = field

FastAPI Patterns

Route Organization

from fastapi import APIRouter, Depends, HTTPException, status

router = APIRouter(prefix="/users", tags=["users"])

@router.get("/", response_model=list[UserResponse])
async def list_users(
    skip: int = 0,
    limit: int = 100,
    db: Session = Depends(get_db),
) -> list[UserResponse]:
    return await user_service.list_users(db, skip=skip, limit=limit)

@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
    user_id: int,
    db: Session = Depends(get_db),
) -> UserResponse:
    user = await user_service.get_user(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found",
        )
    return user

Pydantic Models

from pydantic import BaseModel, EmailStr, Field

class UserCreate(BaseModel):
    email: EmailStr
    name: str = Field(..., min_length=1, max_length=100)
    age: int = Field(..., ge=0, le=150)

class UserResponse(BaseModel):
    id: int
    email: str
    name: str
    created_at: datetime

    model_config = {"from_attributes": True}

Dependency Injection

from fastapi import Depends

async def get_db() -> AsyncGenerator[Session, None]:
    async with async_session() as session:
        yield session

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

@router.get("/me")
async def get_me(user: User = Depends(get_current_user)) -> UserResponse:
    return user

Error Handling

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(ValidationError)
async def validation_error_handler(
    request: Request,
    exc: ValidationError,
) -> JSONResponse:
    return JSONResponse(
        status_code=422,
        content={
            "error": {
                "code": "VALIDATION_ERROR",
                "message": str(exc),
                "field": exc.field,
            }
        },
    )

@app.exception_handler(NotFoundError)
async def not_found_handler(
    request: Request,
    exc: NotFoundError,
) -> JSONResponse:
    return JSONResponse(
        status_code=404,
        content={
            "error": {
                "code": "NOT_FOUND",
                "message": str(exc),
            }
        },
    )

Style Reference

For additional Python conventions, refer to:

Async Patterns

FastAPI is async-first. Follow these patterns:

Use Async for I/O-Bound Operations

# Bad - blocking call in async context
@router.get("/users/{id}")
async def get_user(id: int):
    return sync_db_call(id)  # Blocks the event loop!

# Good - use async database calls
@router.get("/users/{id}")
async def get_user(id: int, db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(User).where(User.id == id))
    return result.scalar_one_or_none()

Run Sync Code in Thread Pool

from fastapi.concurrency import run_in_threadpool

@router.get("/legacy")
async def call_legacy():
    # Offload blocking operation to thread pool
    result = await run_in_threadpool(blocking_legacy_function, arg1, arg2)
    return result

Async Context Managers

from contextlib import asynccontextmanager

@asynccontextmanager
async def get_db_session():
    session = AsyncSession(engine)
    try:
        yield session
        await session.commit()
    except Exception:
        await session.rollback()
        raise
    finally:
        await session.close()

Settings with Pydantic

Use pydantic-settings for configuration:

from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    # Required settings (no default)
    database_url: str
    secret_key: str
    
    # Optional with defaults
    debug: bool = False
    api_prefix: str = "/api/v1"
    
    # With validation
    max_connections: int = Field(default=10, ge=1, le=100)
    
    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8",
    }

# Singleton pattern
_settings: Settings | None = None

def get_settings() -> Settings:
    global _settings
    if _settings is None:
        _settings = Settings()  # Validates at startup
    return _settings

Background Tasks

Use FastAPI's background tasks for fire-and-forget operations:

from fastapi import BackgroundTasks

async def send_welcome_email(email: str):
    # This runs after the response is sent
    await email_service.send(email, template="welcome")

@router.post("/users", status_code=201)
async def create_user(
    user: UserCreate,
    background_tasks: BackgroundTasks,
    db: AsyncSession = Depends(get_db),
):
    new_user = await user_service.create(db, user)
    
    # Queue background task - doesn't block response
    background_tasks.add_task(send_welcome_email, new_user.email)
    
    return new_user

For long-running jobs, use a task queue (Celery, arq, etc.) instead.

Context Managers

Always use context managers for resource management:

# Files
with open("data.txt") as f:
    content = f.read()

# Database connections
async with async_session() as session:
    result = await session.execute(query)

# Locks
async with asyncio.Lock():
    await modify_shared_resource()

# HTTP clients
async with httpx.AsyncClient() as client:
    response = await client.get(url)

Dataclasses vs Pydantic

Choose based on use case:

from dataclasses import dataclass
from pydantic import BaseModel

# Pydantic: API boundaries (validates external data)
class UserCreate(BaseModel):
    email: EmailStr
    name: str = Field(..., min_length=1)

# Dataclass: Internal domain models (no validation overhead)
@dataclass
class User:
    id: int
    email: str
    name: str
    created_at: datetime

Guidelines:

  • Pydantic: Request/response models, config, any external input
  • Dataclass: Internal data structures, domain models, DTOs between layers