npx skills add https://github.com/tenlisboa/.claude --skill python-fastapi-aiSKILL.md
Helper Scripts
scripts/linter-formatter.sh- Formats Python code with ruff, always execute with --help flag first
AI Engineering Reference
For detailed AI Engineering patterns (RAG, tool calling, LangChain, Google ADK), refer to references/ai-engineering.md
FastAPI Development Patterns
Project Structure
Module-Functionality Structure (Recommended for larger apps)
app/
├── core/
│ ├── config.py # Settings with pydantic-settings
│ ├── security.py # Auth utilities
│ └── deps.py # Shared dependencies
├── models/ # SQLAlchemy models
├── schemas/ # Pydantic schemas
├── api/
│ ├── v1/
│ │ ├── endpoints/
│ │ └── router.py
│ └── deps.py
├── services/ # Business logic
├── repositories/ # Data access layer
└── main.py
Async Best Practices
Route Definition
- Use
async deffor I/O-bound operations (DB, HTTP calls) - Use
deffor CPU-bound operations (FastAPI runs in threadpool) - Never mix blocking calls in async routes
@router.get("/users/{user_id}")
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
return await user_service.get_by_id(db, user_id)
@router.post("/process")
def process_cpu_intensive(data: ProcessRequest):
return heavy_computation(data)
Route Path Conventions (Trailing Slashes)
Be consistent - FastAPI returns 307 redirects when trailing slash doesn't match.
Recommended: NO trailing slashes (simpler, matches REST conventions)
router = APIRouter(prefix="/users", tags=["users"])
# Root collection routes - NO trailing slash
@router.post("") # POST /users
@router.get("") # GET /users
# Resource routes - NO trailing slash
@router.get("/{user_id}") # GET /users/{id}
@router.patch("/{user_id}") # PATCH /users/{id}
@router.delete("/{user_id}") # DELETE /users/{id}
# Nested routes - NO trailing slash
@router.get("/{user_id}/posts") # GET /users/{id}/posts
@router.post("/{user_id}/posts") # POST /users/{id}/posts
Alternative: WITH trailing slashes (if you prefer)
@router.post("/") # POST /users/
@router.get("/") # GET /users/
NEVER mix - pick one style and use it everywhere.
Async Patterns
async def fetch_multiple():
async with httpx.AsyncClient() as client:
tasks = [client.get(url) for url in urls]
return await asyncio.gather(*tasks)
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_data())
task2 = tg.create_task(process_data())
Dependencies
Reusable Dependencies
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db)
) -> User:
return await auth_service.validate_token(db, token)
CurrentUser = Annotated[User, Depends(get_current_user)]
@router.get("/me")
async def read_current_user(user: CurrentUser):
return user
Dependency for Validation
async def valid_post_id(post_id: UUID, db: AsyncSession = Depends(get_db)) -> Post:
post = await post_repo.get(db, post_id)
if not post:
raise HTTPException(status_code=404, detail="Post not found")
return post
ValidPost = Annotated[Post, Depends(valid_post_id)]
Service Layer Pattern
- Keep controllers thin (max 10 lines)
- Place business logic in Service classes
- NEVER name methods
listin classes with SQLAlchemy models - shadows Python's builtinlisttype, breaking type hints likelist[Model]later in the class. Uselist_all,find_all, orget_allinstead.
class UserService:
def __init__(self, db: AsyncSession):
self.db = db
async def create(self, data: UserCreate) -> User:
...
# WRONG: shadows builtin, breaks `list[User]` type hint below
async def list(self, page: int = 1) -> tuple[list[User], int]:
...
# CORRECT: use list_all instead
async def list_all(self, page: int = 1, limit: int = 20) -> tuple[list[User], int]:
stmt = select(User).offset((page - 1) * limit).limit(limit)
result = await self.db.execute(stmt)
return list(result.scalars().all()), total
Pydantic v2 Best Practices
Schema Design
from pydantic import BaseModel, Field, EmailStr, ConfigDict
from typing import Annotated
class UserBase(BaseModel):
email: EmailStr
name: Annotated[str, Field(min_length=1, max_length=100)]
class UserCreate(UserBase):
password: Annotated[str, Field(min_length=8)]
class UserResponse(UserBase):
id: int
model_config = ConfigDict(from_attributes=True)
class UserUpdate(BaseModel):
email: EmailStr | None = None
name: str | None = None
Validators
from pydantic import field_validator, model_validator
class OrderCreate(BaseModel):
items: list[OrderItem]
discount_code: str | None = None
@field_validator("items")
@classmethod
def validate_items_not_empty(cls,
...
Repository
tenlisboa/.claudeParent repository
Repository Stats
Stars5
Forks0