Guia de pyproject.toml: Packaging, Dependências e Ferramentas
Tradução automática Este artigo foi traduzido automaticamente a partir da versão original em inglês.
Os projetos Python mais antigos distribuíam frequentemente a configuração por setup.py, setup.cfg, ficheiros de requirements, MANIFEST.in e ficheiros separados para ferramentas de desenvolvimento. O pyproject.toml oferece um local comum para estas preocupações, embora não substitua todos os ficheiros do projeto nem transforme todas as secções numa única norma.
Este guia percorre o ficheiro, desde a configuração do build até aos metadados do projeto e às definições das ferramentas. Quatro responsáveis mantêm as secções distintas: o que faz o build do projeto, o que o projeto publica, o que os contribuidores precisam localmente e o que cada ferramenta configura.
Em resumo. Use
[build-system]para o backend que cria uma distribuição,[project]para os metadados publicados e os requisitos de runtime,[dependency-groups]para ambientes de desenvolvimento não publicados e[tool.*]apenas quando a documentação dessa ferramenta o indicar. Uma declaração de dependências não é um lockfile.
Um ficheiro, quatro responsáveis
Três normas de packaging estabeleceram a estrutura principal:
- PEP 518 define os requisitos do build-system.
- PEP 621 define os metadados do projeto.
- PEP 735 define grupos de dependências não publicados.
Os autores de ferramentas também podem reivindicar um namespace abaixo de [tool]. Não existe uma norma que uniformize o conteúdo dessa área: cada ferramenta define as suas próprias chaves e comportamentos.
Eis uma pequena biblioteca empacotada:
[build-system]
requires = ["hatchling>=1.26"]
build-backend = "hatchling.build"
[project]
name = "weather-client"
version = "0.1.0"
description = "A small weather API client"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.27",
]
[project.optional-dependencies]
cli = ["rich>=13"]
[project.scripts]
weather = "weather_client.cli:main"
[dependency-groups]
test = ["pytest>=8", "pytest-cov>=5"]
lint = ["ruff>=0.12"]
[tool.pytest.ini_options]
testpaths = ["tests"]
[tool.ruff]
line-length = 100
target-version = "py311"
Cada lista de dependências responde a uma pergunta diferente.
[build-system]: como o código-fonte se torna uma distribuição
Um build frontend como python -m build, pip ou uv invoca um build backend. O backend decide como a árvore de código-fonte se transforma num sdist ou wheel e quais os ficheiros que entram nesses artefactos.
[build-system]
requires = ["hatchling>=1.26"]
build-backend = "hatchling.build"
requires contém as dependências necessárias para executar o backend no seu ambiente de build isolado. Não é a lista de dependências de runtime do seu package.
Declare um build system quando o projeto produz uma distribuição ou precisa de instalar o próprio código como um package. Um projeto que não seja um package pode continuar a usar pyproject.toml; a PEP 735 permite inclusivamente um ficheiro que contenha apenas grupos de dependências. Os gestores de ambientes diferem na forma como tratam um projeto sem build system, por isso tome essa decisão deliberadamente.
Escolha o backend com base nos requisitos do build:
- layout pure-Python e necessidades de seleção de ficheiros
- extensões compiladas ou sistemas de build externos
- requisitos de versões dinâmicas ou de ficheiros gerados
- comportamento de editable install
- maturidade do backend no release pipeline
Copie a tabela recomendada atualmente pelo backend a partir da respetiva documentação. Não adicione wheel aos requisitos de build por hábito; é o backend que declara aquilo de que necessita.
[project]: os metadados que os consumidores recebem
A tabela [project] descreve a distribuição: o nome, a versão, a compatibilidade com Python, as dependências de runtime, os pontos de entrada e outros metadados do índice.
[project]
name = "weather-client"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27"]
Os especificadores de dependências são restrições para um resolver, não um snapshot de um ambiente específico. Passam a fazer parte dos metadados do wheel e do sdist, permitindo que os instaladores a jusante os combinem com os requisitos de outros packages.
Extras são uma interface pública de instalação
[project.optional-dependencies] define extras que os consumidores podem solicitar:
[project.optional-dependencies]
cli = ["rich>=13"]
postgres = ["psycopg[binary]>=3.2"]
Um consumidor pode instalar weather-client[cli]. Como os nomes dos extras e os requisitos são publicados, trate-os como capacidades do produto. Não use um extra chamado dev apenas para incluir todas as ferramentas dos contribuidores.
Os pontos de entrada ligam comandos instalados ao Python
[project.scripts]
weather = "weather_client.cli:main"
Depois de a distribuição ser instalada, o ambiente disponibiliza weather, que importa e chama weather_client.cli:main. Teste o comando a partir de um wheel criado, não apenas a partir da raiz do repositório; é o wheel que os utilizadores recebem.
[dependency-groups]: ambientes locais não publicados
Os grupos de dependências da PEP 735 descrevem ambientes de desenvolvimento ou que não são packages, sem os publicar como metadados do package.
[dependency-groups]
test = ["pytest>=8", "pytest-cov>=5"]
lint = ["ruff>=0.12"]
dev = [
{ include-group = "test" },
{ include-group = "lint" },
]
Esta é a fronteira adequada para testes, linters, builders de documentação e ferramentas semelhantes dos contribuidores. Também é útil para aplicações ou notebooks que não criam uma distribuição.
Os grupos de dependências são dados normalizados, mas as interfaces dos instaladores continuam a variar. Verifique como o gestor de ambientes escolhido os instala, cria os locks e resolve. A PEP 735 não define uma interface universal de linha de comandos.
[tool.*]: configuração pertencente a uma única ferramenta
As tabelas das ferramentas não partilham um schema:
[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]
[tool.ruff]
line-length = 100
Use uma tabela [tool.*] apenas se a ferramenta a documentar. Algumas definições continuam a pertencer aos seus próprios ficheiros porque outro ecossistema as consome, porque o ficheiro precisa de um formato diferente ou porque a configuração é mais clara quando fica isolada. O pyproject.toml é um ponto de coordenação. Não exige a centralização de todas as definições.
Declarações, locks e ficheiros de requirements resolvem problemas diferentes
Uma fonte comum de confusão é tratar todos os artefactos de dependências como fontes de verdade concorrentes.
| Artefacto | Objetivo principal | Conteúdo típico |
|---|---|---|
[project.dependencies] | Contrato público de runtime | Requisitos diretos e intervalos compatíveis |
[project.optional-dependencies] | Capacidades opcionais publicadas | Extras destinados aos consumidores |
[dependency-groups] | Ambientes locais não publicados | Grupos de testes, lint, documentação ou aplicações |
| Lockfile | Reproduzir um ambiente resolvido | Versões exatas, fontes e metadados da resolução |
requirements.txt | Input de instalação compatível com pip | Requisitos e opções específicas do pip, constraints, URLs ou hashes |
Uma biblioteca publica normalmente constraints compatíveis e testa um intervalo de versões. Uma aplicação costuma versionar o lockfile do gestor de ambientes. Os pins exatos pertencem ao artefacto de deployment resolvido, não aos metadados públicos de uma biblioteca de forma indiscriminada.
Mantenha um ficheiro de requirements quando uma integração exigir o formato ou as funcionalidades do pip. Se um projeto gerido por uv precisar de um, exporte-o a partir do lock em vez de manter dois conjuntos independentes de dependências:
uv export --format requirements.txt --output-file requirements.txt
O export é uma saída de compatibilidade derivada. O lock continua a ser a fonte resolvida para esse workflow.
Uma migração que preserva o comportamento
Não comece por apagar setup.py ou requirements.txt. Primeiro, classifique a função de cada ficheiro existente.
- Faça o inventário da lógica de build, dos metadados, dos requisitos de runtime, dos extras, dos ambientes de desenvolvimento, das definições das ferramentas, dos dados do package e dos pontos de entrada.
- Selecione um backend capaz de reproduzir a inclusão atual de ficheiros, os artefactos compilados e os editable installs.
- Mova os metadados estáticos publicados para
[project]; mantenha explícitos os campos genuinamente dinâmicos. - Mova os requisitos exclusivos dos contribuidores para
[dependency-groups], não para extras públicos. - Mova as definições das ferramentas apenas quando a ferramenta suportar semântica equivalente.
- Crie um sdist e um wheel, inspecione o respetivo conteúdo e instale o wheel num ambiente limpo.
- Execute os pontos de entrada, os testes, as verificações de import e o percurso real de deployment.
- Apague a configuração antiga apenas depois de os artefactos e o comportamento coincidirem.
MANIFEST.in pode continuar a ser necessário com alguns layouts de setuptools, e um pequeno setup.py pode continuar a ser válido para comportamento programático do build. A modernização transfere a responsabilidade entre ficheiros. Apagar ficheiros não é o objetivo.
Um workflow atual com uv
O uv distingue aplicações de bibliotecas ao criar um projeto:
# Non-library application template
uv init weather-app
# Packaged library with a src layout and build system
uv init --lib weather-client
uv init cria os ficheiros do projeto. A primeira operação do projeto, como uv run, uv sync ou uv lock, cria o lockfile e o .venv persistente conforme necessário.
cd weather-client
uv add httpx
uv add --group test pytest pytest-cov
uv run pytest
uv build
Atualmente, o uv coloca os requisitos de desenvolvimento locais em grupos de dependências normalizados. O seu backend nativo uv_build é uma opção para projetos pure-Python; as extensões compiladas precisam de uma alternativa adequada, como maturin ou scikit-build-core. Consulte a documentação de configuração de projetos do uv para conhecer as definições atuais do backend.
Conclusão
pyproject.toml é claro quando cada tabela tem um único público. O isolamento do build pertence a [build-system] e o comportamento publicado a [project]. Os ambientes dos contribuidores pertencem a [dependency-groups]. As opções das ferramentas pertencem à ferramenta que as define.
Quando estas fronteiras estão estáveis, o ficheiro torna-se mais fácil de rever e as migrações deixam de confundir os metadados do package com o ambiente resolvido de uma máquina.