Tradução automática
Este artigo foi traduzido automaticamente a partir da versão original em inglês.
pyproject.toml Guide: Empacotamento Python, Dependências e Configuração de Ferramentas
Se alguma vez abriste um projeto Python e tentaste perceber onde vivem realmente as dependências, as definições de build e as configurações das ferramentas, sabes bem a dor. setup.py, setup.cfg, requirements.txt, MANIFEST.in, mais um punhado de dotfiles para cada linter e formatter — tudo a ler de sítios diferentes.
pyproject.toml concentra a maior parte disso num único ficheiro.
Resumo rápido
pyproject.toml é, grosso modo, o package.json do Python. Um único ficheiro contém os metadados do projeto, as dependências e as definições das ferramentas. Quer uses .venv, pyenv ou uv, pôr tudo aqui torna a configuração e a colaboração mais simples.
O que é pyproject.toml?
É um ficheiro de configuração que fica na raiz do teu projeto Python, escrito em TOML (pensa em ficheiros INI, mas com uma especificação a sério). Duas PEPs definiram o que ele faz hoje:
- PEP 518 (2016) introduziu a tabela
[build-system]para que as ferramentas de build pudessem declarar os seus requisitos de forma normalizada. - PEP 621 (2020) acrescentou a tabela
[project]para os metadados principais do pacote: nome, versão, dependências, esse tipo de coisa.
Hoje, a maioria das ferramentas do ecossistema Python (Black, isort, pytest, Ruff, mypy) lê a sua configuração de secções [tool.*] neste ficheiro.
Porque é importante
Menos ficheiros para perseguir
Um projeto legacy típico geria setup.py, setup.cfg, requirements.txt, MANIFEST.in e uma série de dotfiles (.flake8, .coveragerc e semelhantes). A maior parte disso passa a estar concentrada num só sítio.
Builds independentes do backend
Quando executas pip install ., o pip lê pyproject.toml e instala as ferramentas de build de que o teu projeto precisa: setuptools, flit, hatchling, o que preferires. Já não ficas preso ao setuptools.
Um único sítio para a configuração das ferramentas
Linters, formatters, test runners e type checkers já sabem que devem procurar aqui. O teu IDE, pipeline de CI e colegas de equipa leem todos do mesmo ficheiro.
Anatomia de um pyproject.toml
Um ficheiro típico tem três secções principais:
# 1. Build system - tells pip/build how to package your project
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# 2. Project metadata and dependencies
[project]
name = "awesome-app"
version = "0.1.0"
description = "Short demo of pyproject.toml"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.111",
"uvicorn[standard]>=0.30",
]
# Expose CLI commands
[project.scripts]
awesome-cli = "awesome_app.cli:main"
# Optional dependencies (e.g., for development)
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
# 3. Tool configuration
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.pytest.ini_options]
addopts = "-ra -q"
testpaths = ["tests"]
A decomposição
[build-system]
Obrigatório se quiseres empacotar ou distribuir o teu projeto. Diz ao pip e às ferramentas de build (como python -m build) qual o backend a usar.
[project]
Os metadados do teu pacote. É aqui que vivem as dependências em vez de requirements.txt.
dependencies: os requisitos de runtime do teu pacote.optional-dependencies: grupos de dependências extra (dev,test,docs).scripts: cria comandos executáveis. No exemplo acima, instalar o pacote dá-te um comandoawesome-clique executa a funçãomainemawesome_app/cli.py.
[tool.*]
Configuração para qualquer ferramenta que o suporte. Cada ferramenta tem o seu próprio namespace: [tool.pytest.ini_options], [tool.mypy], [tool.ruff] e por aí fora.
Substitui requirements.txt?
Para workflows modernos, sim. Ferramentas como Poetry, PDM, Hatch e uv guardam as dependências diretamente na secção [project] e geram lockfiles para reprodutibilidade.
Provavelmente ainda vais querer requirements.txt se:
- Estás a trabalhar com um sistema de deployment legacy que o espera.
- Tens um script de CI que ainda não foi atualizado.
A maioria das ferramentas modernas consegue exportar um a partir do teu pyproject.toml quando necessário:
uv export > requirements.txt
Escolher um backend de build
O backend de build é uma das decisões mais confusas. Aqui vai uma comparação rápida:
| Backend | Melhor para | Prós | Contras |
|---|---|---|---|
| Hatchling | Padrão moderno | Rápido, extensível, suporta plugins | Mais recente, menos suporte legacy |
| Flit | Pacotes simples | Extremamente simples, zero configuração | Não serve para builds complexos (extensões C) |
| Setuptools | Legacy, complexo | Suporta tudo (extensões C, etc.) | Mais lento, mais configuração |
| Poetry | Utilizadores Poetry | Integrado com o ecossistema do Poetry | Preso ao workflow do Poetry |
Para novos projetos pure-Python, Hatchling é um valor por defeito razoável. É o que uv init escreve por ti.
Migrar um projeto existente
Se tens um projeto Python legacy, eis como o modernizar:
- Adiciona
[build-system]. Se não tens a certeza de que backend usar, começa comrequires = ["setuptools>=61", "wheel"]. - Move os metadados para
[project]. Transfere nome, versão e dependências desetup.pyousetup.cfg. - Converte as dependências de desenvolvimento. Coloca-as em
[project.optional-dependencies].dev. - Configura as ferramentas. Adiciona secções
[tool.*]para Black, pytest, mypy, etc. - Decide o que fazer com
requirements.txt. Ou o eliminas, ou o geras a partir do teu lockfile para sistemas legacy que precisem dele.
Depois da migração, normalmente podes apagar setup.py, setup.cfg e a maior parte desses dotfiles de configuração.
Algumas funcionalidades úteis
Pontos de entrada CLI
Em vez do antigo bloco console_scripts em setup.py, usa [project.scripts]:
[project.scripts]
my-tool = "my_package.main:run"
Quando alguém instala o teu pacote, pode escrever my-tool no terminal.
Workspaces (monorepos)
Ferramentas como uv e hatch suportam workspaces, que te permitem gerir vários pacotes num único repositório:
[tool.uv.workspace]
members = ["packages/*"]
Podes desenvolver vários pacotes interdependentes e instalá-los todos no mesmo ambiente virtual para testes.
Workflows típicos com uv
Começar um novo projeto
uv init my_app # creates folder with pyproject.toml and .venv
cd my_app
uv add requests fastapi # adds to [project.dependencies] and installs
uv run pytest # runs tests in the venv
Executar scripts
Podes definir scripts em pyproject.toml (usando um task runner como poe ou hatch) ou simplesmente chamar uv run:
uv run python main.py
Dicas práticas
- Não fixes versões exatas em bibliotecas. Usa intervalos como
requests>=2.30para que a tua biblioteca não entre em conflito com o que quer que esteja instalado no ambiente de alguém. - Fixa versões em aplicações. Usa um lockfile (
uv.lock,poetry.lock) para builds reprodutíveis. - Agrupa as dependências de desenvolvimento. Mantém as dependências de testes, linting e documentação em grupos opcionais separados (
dev,test,docs). - Não despejes todas as opções em
pyproject.toml. Fica-te pelos valores por defeito ao nível do projeto e deixa os ficheiros de configuração específicos de cada ferramenta tratar do resto, se ficar demasiado confuso.
A grande vantagem de pyproject.toml é sobretudo eliminar pequenas fricções do dia a dia. Deixas de andar à procura de que ficheiro controla o build. A configuração do linter fica ao lado das dependências. O pip e o teu IDE finalmente concordam sobre o que está instalado. Escolhe um backend de build, coloca as tuas dependências em [project] e deixa as ferramentas lerem de um único sítio. Se estás a começar do zero, uv init leva-te a maior parte do caminho com um único comando.