Ir para o conteúdo

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.

Estrutura

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.

Ecossistema de Ferramentas

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 comando awesome-cli que executa a função main em awesome_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:

Fluxo de Migração

  1. Adiciona [build-system]. Se não tens a certeza de que backend usar, começa com requires = ["setuptools>=61", "wheel"].
  2. Move os metadados para [project]. Transfere nome, versão e dependências de setup.py ou setup.cfg.
  3. Converte as dependências de desenvolvimento. Coloca-as em [project.optional-dependencies].dev.
  4. Configura as ferramentas. Adiciona secções [tool.*] para Black, pytest, mypy, etc.
  5. 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

  1. Não fixes versões exatas em bibliotecas. Usa intervalos como requests>=2.30 para que a tua biblioteca não entre em conflito com o que quer que esteja instalado no ambiente de alguém.
  2. Fixa versões em aplicações. Usa um lockfile (uv.lock, poetry.lock) para builds reprodutíveis.
  3. Agrupa as dependências de desenvolvimento. Mantém as dependências de testes, linting e documentação em grupos opcionais separados (dev, test, docs).
  4. 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.