[!NOTE] Tradução automática Este artigo foi traduzido automaticamente a partir da versão original em inglês.

pyproject.toml Guia: Empacotamento em Python, Dependências e Configuração de Ferramentas

Projetos Python mais antigos costumam distribuir a configuração por todo o setup.py, setup.cfg, ficheiros de requisitos, MANIFEST.ine arquivos separados para as ferramentas de desenvolvimento. pyproject.toml fornece um local comum para abordar essas preocupações, embora não substitua cada ficheiro de projeto nem torne cada secção parte de um padrão único.

Este guia explora o ficheiro, desde a configuração de compilação até aos metadados do projeto e às definições das ferramentas. A forma mais simples de o manter compreensível é separar as responsabilidades em quatro áreas distintas: o que compila o projeto, o que o projeto publica, o que os colaboradores necessitam localmente e o que as ferramentas individuais configuram.

TL;DR. Utilize [build-system] para o backend que constrói uma distribuição, [project] para metadados publicados e os requisitos de runtime, [dependency-groups] para ambientes de desenvolvimento não publicados, e [tool.*] Apenas nos locais indicados na documentação daquela ferramenta. Uma declaração de dependência não é um ficheiro de bloqueio.

Um ficheiro, quatro proprietários

Os quatro limites de propriedade no pyproject.toml

Três padrões de embalagem definiram a estrutura fundamental:

Os autores de ferramentas também podem reivindicar um espaço de nomes abaixo [tool]. Essa área é convencional, e não universal: cada ferramenta define as suas próprias chaves e comportamentos.

Esta é uma pequena biblioteca embalada:

[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. Essa distinção é o elemento central do ficheiro.

[build-system]: como uma fonte se torna uma distribuição

Uma compilação frontend como python -m buildO pip ou o uv acionam um processo de compilação backend. O backend determina como a árvore de fontes é transformada num sdist ou num wheel, bem como quais ficheiros são incluídos nesses artefatos.

[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 compilação isolado. Não faz parte da lista de dependências runtime do seu pacote.

Declare um sistema de compilação quando o projeto gera uma distribuição ou necessita que o seu próprio código seja instalado como um pacote. Um projeto que não é um pacote ainda pode utilizar pyproject.toml; O PEP 735 permite mesmo que um ficheiro contenha apenas grupos de dependências. Os gestores de ambiente diferem na forma como tratam um projeto sem sistema de compilação, pelo que deve tomar essa decisão de forma deliberada.

Escolha o backend entre os requisitos de compilação:

Copie a tabela atualmente recomendada pelo backend a partir da sua documentação. Não adicione nada. wheel Para definir os requisitos de forma habitual, o backend especifica quais são as necessidades.

[project]: os consumidores de metadados recebem

O [project] A tabela descreve a distribuição: o seu nome, versão, compatibilidade com Python, as dependências runtime, 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ência são restrições para um resolvedor, e não uma representação de um ambiente específico. Eles passam a fazer parte dos metadados dos ficheiros wheel e sdist, permitindo que os instaladores subsequentes os combinem com as exigências de outros pacotes.

Os extras constituem uma interface de instalação pública

[project.optional-dependencies] define os recursos adicionais que os consumidores podem solicitar:

[project.optional-dependencies]
cli = ["rich>=13"]
postgres = ["psycopg[binary]>=3.2"]

Um utilizador final pode instalar weather-client[cli]. Como são publicados nomes e requisitos adicionais, trate-os como funcionalidades do produto. Não utilize um nome adicional dev Apenas para armazenar as ferramentas de cada colaborador.

Os pontos de entrada ligam os comandos instalados ao Python

[project.scripts]
weather = "weather_client.cli:main"

Após a instalação da distribuição, o ambiente torna acessíveis weather, que importa e chama weather_client.cli:main. Teste o comando a partir de um ficheiro wheel compilado, e não apenas a partir da raiz do repositório; é o wheel que os utilizadores recebem.

[dependency-groups]: ambientes locais e não publicados

Os grupos de dependências PEP 735 descrevem ambientes de desenvolvimento ou que não são pacotes, sem que estes sejam publicados como metadados de pacote.

[dependency-groups]
test = ["pytest>=8", "pytest-cov>=5"]
lint = ["ruff>=0.12"]
dev = [
  { include-group = "test" },
  { include-group = "lint" },
]

Este é o limite correto para testes, ferramentas de linting, geradores de documentação e outras ferramentas semelhantes destinadas a colaboradores. É também útil para aplicações ou notebooks que não criam uma distribuição.

Os grupos de dependências são dados padronizados, mas as interfaces dos instaladores continuam a variar. Verifique como o gestor de ambiente selecionado os instala, bloqueia e resolve. O PEP 735 não define uma interface de linha de comandos universal.

[tool.*]: configuração pertencente a uma ferramenta específica

As tabelas de ferramentas não partilham um esquema:

[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]

[tool.ruff]
line-length = 100

Utilizar um [tool.*] Uma tabela apenas se a ferramenta a documentar. Algumas definições continuam a estar nos seus próprios ficheiros porque outro ecossistema as consome, o ficheiro requer um formato diferente ou a configuração fica mais clara quando está isolada. pyproject.toml Trata‑se de um ponto de coordenação, e não de uma diretiva para centralizar tudo.

Consumidores de configuração em torno do pyproject.toml

Declarações, bloqueios e ficheiros de requisitos resolvem problemas distintos

A fonte mais comum de confusão é tratar cada artefato de dependência como uma fonte de verdade concorrente.

ArtefatoPropósito principalConteúdos típicos
[project.dependencies]Contrato runtime publicadoRequisitos diretos e intervalos compatíveis
[project.optional-dependencies]Funcionalidades publicadas com consentimento prévioRecursos adicionais direcionados ao consumidor
[dependency-groups]Ambientes locais não publicadosTestes, verificação de formato, documentação ou grupos de aplicações
Ficheiro de bloqueioReproduzir um ambiente resolvidoVersões exatas, fontes e metadados de resolução
requirements.txtentrada de instalação compatível com pipRequisitos, opções específicas do pip, restrições, URLs ou hashes

Uma biblioteca costuma publicar restrições e testes compatíveis para um determinado intervalo. Uma aplicação, por sua vez, geralmente guarda o ficheiro de bloqueio do gestor de ambiente. Os valores exatos devem constar no artefato de deploy finalizado, e não de forma aleatória nos metadados públicos da biblioteca.

Mantenha um ficheiro de requisitos sempre que uma integração exigir o formato ou funcionalidades do pip. Se um projeto gerido por uv precisar de um, exporte-o a partir do lock em vez de manter dois conjuntos de dependências independentes:

uv export --format requirements.txt --output-file requirements.txt

A exportação resulta de uma saída de compatibilidade. O “lock” continua a ser a versão original resolvida para esse fluxo de trabalho.

Uma migração que preserva o comportamento

Uma migração que dá prioridade à verificação para o pyproject.toml

Não comece por eliminar. setup.py ou requirements.txt. Primeiro, classifique a função de cada ficheiro existente.

  1. Lógica de construção do inventário, metadados, requisitos runtime, elementos adicionais, ambientes de desenvolvimento, configurações de ferramentas, dados de pacotes e pontos de entrada.
  2. Selecionar um backend capaz de reproduzir a inclusão atual de ficheiros, os artefatos compilados e as instalações editáveis.
  3. Mover os metadados estáticos publicados para [project]; mantenha os campos verdadeiramente dinâmicos explícitos.
  4. Mova os requisitos exclusivos para colaboradores para [dependency-groups], sem recursos adicionais públicos.
  5. Mova as definições da ferramenta apenas para locais onde ela suporta semânticas equivalentes.
  6. Crie tanto um sdist como um wheel, verifique o seu conteúdo e instale o wheel num ambiente limpo.
  7. Execute os pontos de entrada, os testes, as verificações de importação e o processo real de deploy.
  8. Elimine as configurações antigas somente após os artefatos e o comportamento corresponderem.

MANIFEST.in pode ainda ser necessário em alguns layouts do setuptools, e um pequeno setup.py pode permanecer válido para o comportamento de compilação programática. A modernização consiste numa mudança de propriedade, e não num processo de eliminação de ficheiros.

Um fluxo de trabalho atual para UV

uv diferencia as aplicações das 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 ficheiros de projeto. A primeira operação do projeto, como uv run, uv sync, ou uv lock cria o ficheiro de bloqueio e a persistência .venv conforme necessário.

cd weather-client
uv add httpx
uv add --group test pytest pytest-cov
uv run pytest
uv build

Atualmente, o uv organiza os requisitos de desenvolvimento local em grupos de dependências padronizados. O seu formato nativo uv_build backend é uma opção para projetos em Python puro; extensões compiladas exigem uma alternativa adequada, como o maturin ou o scikit-build-core.

Conclusão

pyproject.toml Fica claro quando cada tabela tem um público-alvo definido. É necessário implementar isolamento para esses públicos. [build-system]. O comportamento publicado pertence a [project]. Os ambientes dos colaboradores pertencem a [dependency-groups]. As alternâncias de ferramenta pertencem à ferramenta que as define.

Assim que esses limites se tornam estáveis, o ficheiro fica mais fácil de analisar — e as migrações deixam de confundir os metadados dos pacotes com o ambiente resolvido de uma determinada máquina.

Referências