Buscar/Faça a IA escrever serviços FastAPI e encontrar erros comuns na revisão

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 |