| name | impl-api |
| description | Implementar routers e schemas da API em api/. Use quando criar endpoint, rota, schema de request/response, ou registrar router. |
| allowed-tools | Read, Write, Edit, Glob, Grep |
Implementar API (Camada de Routers e Schemas)
Regras Arquiteturais (NON-NEGOTIABLE)
- Router SÓ faz:
request → service.method() → response - SEM lógica de negócio em routers (validações, cálculos, etc.)
- SEM prompts de LLM em routers
- SEM acesso direto a banco de dados
- Service via getter: instanciar service em função getter com DI pattern
- Response models: sempre especificar
response_model=nos endpoints - Schemas separados: request/response em
api/schemas/
Estrutura de Arquivos
src/synth_lab/api/
├── main.py # App FastAPI, lifespan, middleware
├── errors.py # Exception handlers
├── routers/
│ └── {entities}.py # Um arquivo por recurso (plural)
└── schemas/
└── {entity}.py # Schemas por entidade
Convenções de nome:
- Router:
{entities}.py(plural:synths.py,experiments.py) - Schema:
{entity}.py(singular) - Sufixos de schema:
Create- request de criaçãoUpdate- request de atualizaçãoSummary- resposta resumida (listas)Detail- resposta completa
Padrões de Código
Router
"""
{Entity} router for synth-lab API.
Endpoints for {entity} operations.
"""
from fastapi import APIRouter, HTTPException, Path, Query
from synth_lab.api.schemas.{entity} import (
{Entity}Create,
{Entity}Detail,
{Entity}Summary,
)
from synth_lab.models.pagination import PaginatedResponse, PaginationParams
from synth_lab.services.{entity}_service import {Entity}Service
router = APIRouter()
def get_{entity}_service() -> {Entity}Service:
"""Get {entity} service instance (DI pattern)."""
return {Entity}Service()
@router.get("/list", response_model=PaginatedResponse[{Entity}Summary])
async def list_{entities}(
limit: int = Query(default=50, ge=1, le=200, description="Items per page"),
offset: int = Query(default=0, ge=0, description="Items to skip"),
sort_by: str | None = Query(default=None, description="Sort field"),
sort_order: str = Query(default="desc", pattern="^(asc|desc)$"),
) -> PaginatedResponse[{Entity}Summary]:
"""List {entities} with pagination."""
service = get_{entity}_service()
params = PaginationParams(
limit=limit,
offset=offset,
sort_by=sort_by,
sort_order=sort_order,
)
return service.list_{entities}(params)
@router.get("/{{{entity}_id}}", response_model={Entity}Detail)
async def get_{entity}(
{entity}_id: str = Path(..., description="{Entity} ID"),
) -> {Entity}Detail:
"""Get {entity} by ID."""
service = get_{entity}_service()
return service.get_{entity}({entity}_id)
@router.post("/", response_model={Entity}Detail, status_code=201)
async def create_{entity}(
data: {Entity}Create,
) -> {Entity}Detail:
"""Create new {entity}."""
service = get_{entity}_service()
return service.create_{entity}(
name=data.name,
description=data.description,
)
@router.delete("/{{{entity}_id}}", status_code=204)
async def delete_{entity}(
{entity}_id: str = Path(..., description="{Entity} ID"),
) -> None:
"""Delete {entity} by ID."""
service = get_{entity}_service()
service.delete_{entity}({entity}_id)
Schemas
"""
{Entity} schemas for API request/response.
"""
from datetime import datetime
from pydantic import BaseModel, Field
class {Entity}Create(BaseModel):
"""Schema for creating a new {entity}."""
name: str = Field(..., min_length=1, max_length=100, description="Name")
description: str | None = Field(default=None, max_length=500)
class {Entity}Update(BaseModel):
"""Schema for updating an {entity}."""
name: str | None = Field(default=None, min_length=1, max_length=100)
description: str | None = Field(default=None, max_length=500)
class {Entity}Summary(BaseModel):
"""Summary for list responses."""
id: str = Field(..., description="{Entity} ID")
name: str = Field(..., description="Name")
created_at: datetime = Field(..., description="Creation timestamp")
class {Entity}Detail({Entity}Summary):
"""Full details for single item responses."""
description: str | None = Field(default=None)
updated_at: datetime | None = Field(default=None)
# Nested models
metadata: dict | None = Field(default=None)
Registrar Router em main.py
# Em api/main.py
from synth_lab.api.routers import {entities}
# Registrar router
app.include_router(
{entities}.router,
prefix="/{entities}",
tags=["{entities}"],
)
Exception Handlers (api/errors.py)
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from synth_lab.services.errors import NotFoundError, ValidationError
def register_exception_handlers(app: FastAPI) -> None:
"""Register all exception handlers."""
@app.exception_handler(NotFoundError)
async def not_found_handler(request: Request, exc: NotFoundError):
return JSONResponse(
status_code=404,
content={"error": {"code": exc.code, "message": exc.message}},
)
@app.exception_handler(ValidationError)
async def validation_handler(request: Request, exc: ValidationError):
return JSONResponse(
status_code=422,
content={"error": {"code": exc.code, "message": exc.message}},
)
Checklist de Verificação
Antes de finalizar, verificar:
- Router só faz
request → service → response - SEM lógica de negócio no router
- Service instanciado via getter function
-
response_model=em todos os endpoints - Schemas em arquivo separado (
api/schemas/) - Schemas usam sufixos corretos (Create, Summary, Detail)
- Query/Path validators com descriptions
- Router registrado em
main.pycom prefix e tags - Exception handlers para erros de domínio
- Docstrings em todos os endpoints