


Faça a IA escrever serviços FastAPI e encontrar erros comuns na revisão
Use quando uma IA cria ou revisa um serviço FastAPI. Define as versões mínimas, quando usar rotas async ou sync, como escrever modelos Pydantic e dependências, como lidar com o banco de dados e os testes, e traz uma tabela de erros comuns em um diff, com a correção.
Para usar estas regras de IA
## Defina as versões mínimas de Python, FastAPI e das bibliotecas
Fixe cada dependência na versão abaixo ou mais recente; toda a orientação aqui parte dessas bases. Uma versão mais antiga quebra silenciosamente os padrões usados nas seções seguintes — APIs assíncronas, dependências `Annotated` e a serialização v2.
| Dependência | Mínimo | Por que esse piso |
|---|---|---|
| Python | 3.11 | `StrEnum` e o operador de união `X \| Y` |
| FastAPI | 0.115 | `Annotated[T, Depends(...)]` é a forma idiomática |
| Pydantic | 2.7 | As APIs da v1 (`json_encoders`, `.dict()`) foram removidas |
| pydantic-settings | 2.4 | Pacote separado desde o Pydantic v2 |
| SQLAlchemy | 2.0 | A API assíncrona (`AsyncSession`, `async_sessionmaker`) |
| Alembic | 1.13 | Migrações compatíveis com assíncrono |
| httpx | 0.27 | `ASGITransport` para testes em processo |
| PyJWT | 2.9 | Mantido ativamente; substitui o `python-jose` |
| ruff | 0.6 | Uma única ferramenta para lint, formatação e ordenação de imports |
## Organize o projeto em pastas por domínio, não por tipo de arquivo
Organize a árvore de arquivos por domínio (bounded context), e não por tipo de arquivo. Dê a cada domínio seu próprio pacote e mantenha uma camada global enxuta para as questões compartilhadas.
- Pacote por domínio: `router.py` (endpoints), `schemas.py` (modelos Pydantic), `models.py` (modelos ORM), `service.py` (lógica de negócio), `dependencies.py`, `config.py` (configurações específicas do domínio), `constants.py`, `exceptions.py`, `utils.py`.
- Nível global: `config.py` (configurações globais), `models.py` (bases compartilhadas), `exceptions.py`, `database.py` (engine assíncrona mais fábrica de sessões), `main.py` (aplicação mais lifespan).
- Imports entre domínios: sempre importe o módulo explícito e dê a ele um alias (por exemplo, importe o `service` de um domínio como `auth_service`). Nunca use um import com wildcard e nunca acesse um caminho interno profundo — ambos acoplam domínios e travam refatorações.
## Escolha rotas async def ou def para a aplicação não travar
Escolha o estilo da rota de acordo com o trabalho que ela executa; a escolha errada trava o event loop ou desperdiça threads.
| A rota faz isto | Use |
|---|---|
| I/O não bloqueante e aguardável | `async def` |
| I/O bloqueante, sem cliente assíncrono disponível | `def` (síncrona, roda na threadpool) |
| Uma mistura dos dois | `async def` mais `run_in_threadpool` para a chamada bloqueante |
| Trabalho limitado por CPU acima de ~50 ms | Delegue a um processo worker (Celery / Arq / RQ) |
Nunca coloque uma chamada bloqueante — `time.sleep`, `requests`, um driver de banco de dados síncrono ou um simples `open()` — dentro de uma `async def`; isso trava todas as requisições daquele worker. Uma rota síncrona `def` bloqueia apenas um worker da threadpool, por isso é o lugar seguro para chamadas bloqueantes inevitáveis. Dentro de `async def`, use equivalentes aguardáveis (`asyncio.sleep`, `aiofiles`, um driver assíncrono) ou envolva uma biblioteca síncrona com `run_in_threadpool`. Ressalvas: a threadpool padrão tem cerca de 40 threads, então saturá-la deixa todas as rotas síncronas mais lentas, e threads custam mais que coroutines — não escolha rotas síncronas sem um motivo bloqueante real.
## Valide dados com Pydantic e separe as configurações por domínio
- Prefira validadores e tipos nativos a verificações feitas à mão: `Field(min_length=, max_length=, pattern=, ge=)`, `EmailStr`, `AnyUrl` e `StrEnum` para conjuntos fechados.
- Não combine uma restrição com um valor padrão contraditório. `Field(ge=18, default=None)` é um bug: decida entre obrigatório (`Field(ge=18)`) ou opcional (`int | None = Field(default=None, ge=18)`).
- Use a serialização moderna. `json_encoders` está descontinuado no Pydantic v2 — substitua por `@field_serializer` para regras por campo, ou anote o tipo com `PlainSerializer`.
- Separe as configurações por domínio. `pydantic-settings` é um pacote próprio; dê a cada domínio uma subclasse `BaseSettings` com seu próprio `env_prefix`, em vez de um único objeto de configurações globais lido por todos os domínios.
## Escreva dependências com Annotated e faça-as validar os dados
- Injete com `Annotated[T, Depends(fn)]`, a forma idiomática desde o FastAPI 0.95. Evite a forma de argumento padrão (`param: T = Depends(fn)`); ela é legada e traz armadilhas de valor padrão.
- Faça as dependências validarem, não apenas injetarem. Uma dependência pode carregar um recurso pelo id, levantar um 404 de domínio quando ele não existir e retornar o objeto validado, para que a rota receba algo já verificado.
- Encadeie dependências para reaproveitar lógica. Uma dependência pode depender de outras — por exemplo, uma verificação de propriedade que depende de uma dependência de carregamento por id e de uma dependência de token já interpretado.
- Regras: as dependências são armazenadas em cache por requisição, então o mesmo `Depends(x)` usado várias vezes em uma requisição roda apenas uma vez. Prefira dependências `async def`; as síncronas rodam na threadpool, um custo desperdiçado para verificações pequenas que só usam CPU. Reaproveite uma dependência entre endpoints dando à variável de caminho compartilhada o mesmo nome.
## Implemente a autenticação JWT e o acesso assíncrono ao banco de dados
- JWT: use `PyJWT` (`import jwt`), não o `python-jose`, que está sem manutenção. Faça a decodificação dentro de uma proteção contra `InvalidTokenError` e levante um erro de credenciais de domínio em vez de deixar a exceção bruta escapar.
- Sessões: prefira a API assíncrona do SQLAlchemy 2.0 (`create_async_engine`, `async_sessionmaker`, `AsyncSession`) e entregue as sessões às rotas por meio de uma dependência `get_db` que seja um gerador assíncrono. `encode/databases` está em modo de manutenção — não o escolha para projetos novos.
- Nomenclatura: use `lower_case_snake`; nomes de tabela no singular (`post`, `user`, `post_like`); agrupe tabelas relacionadas com um prefixo comum (`payment_account`, `payment_bill`); use o sufixo `_at` para datetimes e `_date` para datas; e reaproveite o mesmo nome de coluna de chave estrangeira em todos os lugares onde ela aparece.
- Defina uma única convenção de nomenclatura de índices e restrições, válida para todo o projeto, no `MetaData`, para que os nomes gerados fiquem previsíveis.
- SQL primeiro, Pydantic depois: faça joins, agregações e a formatação do JSON em SQL, e só converta para Pydantic na validação da resposta, não para transformação de dados.
## Escolha entre tarefas em segundo plano e uma fila como o Celery
Escolha a ferramenta de acordo com o custo da tarefa e a confiabilidade necessária.
| Use tarefas em segundo plano no próprio processo quando | Use uma fila de verdade (Celery / Arq / RQ) quando |
|---|---|
| A tarefa termina em menos de um segundo | Ela roda por segundos a minutos |
| Perdê-la silenciosamente é aceitável | Você precisa de retries, dead-letter ou visibilidade |
| Ela roda no próprio processo (enviar um e-mail, registrar uma linha) | Ela é pesada para a CPU ou precisa do próprio pool |
| Não é preciso agendamento | Você precisa de cron, ETA ou limitação de taxa |
Tarefas em segundo plano no próprio processo rodam depois que a resposta é enviada, no mesmo processo worker. Se esse worker morrer, a tarefa se perde e não há retry, então nunca as use para algo que justificaria acionar um engenheiro de plantão.
## Teste com httpx e um banco de dados real
- Use um cliente HTTP assíncrono desde o primeiro dia: rode a aplicação no próprio processo com `httpx.AsyncClient` sobre `ASGITransport(app=app)`. Não use `async_asgi_testclient` — está sem manutenção.
- Substitua dependências com o recurso nativo do framework `app.dependency_overrides[dep] = fake`, em vez de fazer monkeypatch em internals, e limpe as substituições após cada teste.
- Use um banco de dados real nos testes de integração (um schema efêmero ou um container descartável) e substitua apenas autenticação e serviços externos. Simular o banco de dados esconde divergências entre mock e produção que acabam explodindo em produção.
## Mantenha as migrações reversíveis e documente cada endpoint da API
- Migrações: mantenha cada migração estática e reversível, gere-as a partir do template assíncrono e use nomes de arquivo descritivos com timestamp (um prefixo de data mais um slug) para que o histórico fique em ordem.
- Documentação da API: esconda a documentação interativa fora dos ambientes escolhidos, definindo a URL do OpenAPI como `None` quando o ambiente atual não estiver em uma lista de permissões. Documente cada endpoint por completo — modelo de resposta, código de status, resumo, descrição, tags e uma entrada de resposta para cada status de erro.
- Linting: rode o formatador e o linter (por exemplo, `ruff check --fix` e depois `ruff format`) e conecte-os a um hook de pre-commit ou ao CI. Uma única ferramenta moderna substitui o formatador, o ordenador de imports e as ferramentas de código morto separadas.
## Encontre e corrija erros comuns da IA em um diff de FastAPI
Ao revisar um diff, sinalize estes erros comuns de agentes; cada linha é um modo de falha real com sua correção.
| Anti-padrão | Por que está errado | Correção |
|---|---|---|
| `requests.get(...)` dentro de `async def` | Bloqueia o event loop | `httpx.AsyncClient`, ou `await run_in_threadpool(...)` |
| `time.sleep` / `open()` / driver de banco síncrono em `async def` | Bloqueia o loop | Equivalente assíncrono (`asyncio.sleep`, `aiofiles`, driver assíncrono) |
| `from jose import jwt` | Biblioteca sem manutenção | `import jwt` (PyJWT) |
| `async_asgi_testclient` | Sem manutenção | `httpx.AsyncClient` mais `ASGITransport` |
| `ConfigDict(json_encoders=...)` | Descontinuado no Pydantic v2 | `@field_serializer` ou `PlainSerializer` |
| `Field(ge=18, default=None)` | A restrição contradiz o valor padrão | Escolha obrigatório ou opcional, não os dois |
| `Depends(...)` como argumento padrão | Armadilhas legadas de valor padrão | `Annotated[T, Depends(...)]` |
| Capturar `Exception` genérica em torno do corpo de uma rota | Esconde bugs, transforma 500s em 200s silenciosos | Capture a classe específica, levante um erro HTTP explícito |
| Tarefas em segundo plano para trabalho crítico | Sem retry, morre junto com o worker | Celery / Arq / RQ |
| Sessão ORM síncrona dentro de `async def` | Bloqueia o loop, pode causar deadlock no pool | `AsyncSession` |
| Retornar um modelo e também defini-lo como `response_model` | O modelo é construído duas vezes | Retorne um dict ou uma linha, ou remova o `response_model` |
| Imports profundos entre domínios | Acoplamento excessivo | Importe o módulo do domínio e dê a ele um alias |
| Um único `BaseSettings` para toda a aplicação | Todo domínio lê todas as variáveis | Um `BaseSettings` por domínio |
| Simular o banco de dados em testes de integração | Divergência entre mock e produção | Um banco real mais substituições de dependência para autenticação e serviços externos |
