Padronização de pyproject.toml¶
Este documento define as convenções adotadas nos campos do pyproject.toml que não são cobertos por outras normas — especificamente a declaração de dependências de desenvolvimento e a configuração do linter/formatter.
1. Dependências de desenvolvimento — [dependency-groups]¶
Use sempre [dependency-groups] (PEP 735) para declarar dependências de desenvolvimento. Não use [project.optional-dependencies] para esse fim.
[dependency-groups]
dev = [
"pytest>=8.0",
"ruff>=0.9.0",
]
Por que não [project.optional-dependencies]?¶
[project.optional-dependencies] é projetado para extras de runtime instaláveis pelo usuário final (ex: pip install pacote[postgres]). Usar esse mecanismo para dependências de desenvolvimento é um abuso semântico — esses extras aparecem nos metadados publicados do pacote e não fazem sentido fora do contexto de desenvolvimento.
[dependency-groups] foi introduzido exatamente para separar esse caso: dependências que só existem durante o desenvolvimento e não integram os metadados de distribuição.
Instalando o grupo de desenvolvimento¶
# workspace (padrão — instala tudo incluindo dev)
uv sync --all-packages
# pacote isolado
uv sync --dev
Quando [project.optional-dependencies] é correto¶
Use [project.optional-dependencies] somente para extras que o usuário pode instalar opcionalmente em produção:
[project.optional-dependencies]
cli = ["typer>=0.12", "rich>=13"]
Exemplo: quantilica-core expõe quantilica-core[cli] para habilitar helpers de Rich/Typer nos hosts que precisam deles. Esse é o uso correto.
2. Configuração do ruff — localização e precedência¶
Regra de precedência¶
O arquivo ruff.toml na raiz do workspace é canônico. Ele define as regras base para todos os pacotes.
Por pacote, o [tool.ruff] no pyproject.toml deve existir somente para overrides locais — quando um módulo específico precisa suprimir uma regra que não faz sentido no seu contexto.
Quantilica/
├── ruff.toml ← canônico (target-version, select, line-length)
└── meu-fetcher/
└── pyproject.toml ← [tool.ruff] só se houver override local
O que vai no ruff.toml raiz¶
# ruff.toml (raiz do workspace)
target-version = "py312"
exclude = [".venv", ".uv-cache"]
[lint]
select = ["E", "F", "I", "UP", "B"]
Esse arquivo não declara line-length — ele herda o padrão do ruff (88), que é o valor adotado pelo ecossistema.
O que pode ir no [tool.ruff] por pacote¶
Apenas overrides que não fazem sentido subir para o nível workspace:
# pyproject.toml do pacote (apenas overrides)
[tool.ruff.lint.per-file-ignores]
"src/meu_pacote/cli.py" = ["B008"] # default mutável em argparse é intencional
"tests/test_algo.py" = ["E402"] # imports fora do topo por necessidade de mock
O que nunca repetir por pacote¶
Não repita no pyproject.toml do pacote o que já está no ruff.toml raiz:
# NÃO faça isso — duplicação sem ganho
[tool.ruff]
line-length = 88 # já é o default
target-version = "py312" # já está no ruff.toml raiz
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"] # já está no ruff.toml raiz
Quando rodar ruff fora do workspace¶
Se alguém rodar ruff check de dentro do diretório do pacote, sem o ruff.toml da raiz visível, as regras base não serão aplicadas. Isso é aceitável: o cenário normal de desenvolvimento é a partir da raiz do workspace. Repos que precisam funcionar completamente standalone (ex: após extração do workspace) devem replicar a configuração no próprio pyproject.toml.