← Voltar ao Blog
Tutorials & How-Tos

Checklist de build e publicação com uv: quando o backend uv é suficiente e quando não é

uv pode simplificar builds e publicação de pacotes Python, mas não elimina a necessidade de provar que seu wheel ou sua distribuição de código-fonte funciona fora do seu workspace local. Este checklist ajuda mantenedores a decidir quando o backend de build do uv é suficiente, quando outro backend é necessário e como verificar artefatos antes da publicação.

Escrito por Hamza Diaz
3 de outubro de 202610 min de leitura88 visualizações

uv build pode tornar o trabalho de lançamento de pacotes Python mais fácil, mas não torna o wheel ou a distribuição de código-fonte produzidos corretos por padrão. Um projeto pode resolver seu lockfile, passar nos testes a partir do checkout e iniciar dentro do Docker, e ainda assim falhar para alguém que instala o artefato publicado.

Essa falha geralmente vem de uma lacuna simples: um arquivo local, uma dependência por caminho, um diretório de dados do pacote ou uma importação de console script existia no workspace, mas não na distribuição. Este checklist é para mantenedores que querem usar uv sem confundir um repositório funcional com um pacote pronto para lançamento. Os comandos abaixo são verificações propostas para seu projeto. Eles não foram executados neste fluxo de trabalho, e nenhuma saída de comando é inventada.

A regra: teste o artefato, não o checkout

uv dá aos mantenedores uma única superfície de ferramenta para gerenciamento de projeto, builds de distribuição e publicação. O guia oficial de pacotes documenta uv build e uv publish, e o guia de backend documenta uv_build como backend de build. Essas ferramentas são úteis. Ainda assim, elas deixam uma pergunta difícil: um consumidor externo consegue instalar e usar aquilo que você pretende publicar?

Um consumidor recebe metadados e arquivos de um wheel, um sdist ou um índice de pacotes. Ele não recebe seu checkout editável, sua configuração de fontes do workspace, sua dependência privada por caminho, o cache de camadas do Docker ou sua pasta de dados local, a menos que essas partes façam parte do artefato ou sejam alcançáveis por metadados declarados. É por isso que o artefato merece sua própria faixa de testes.

Essa é a mesma mentalidade de evidência por trás de observabilidade de build de engine TensorRT. Uma mensagem de progresso não é o mesmo que prova. Em empacotamento, uma execução local de testes verde não é o mesmo que uma instalação de consumidor a partir de um wheel.

flowchart TD A[Projeto funciona no checkout] --> B[Escolha o backend deliberadamente] B --> C[Construa wheel e sdist] C --> D[Inspecione arquivos pelo nome exato] D --> E[Reconstrua a partir do sdist quando relevante] E --> F[Instale o wheel fora do checkout] F --> G[Importe o módulo e carregue dados do pacote] G --> H{Pronto para publicar?} H -->|Não| B H -->|Sim| I[Publique, depois verifique a partir do índice alvo]

Quando uv_build é suficiente e quando não é

O backend de build do uv é um bom candidato quando o pacote é Python puro e convencional: módulos normais, um layout src claro, metadados diretos, dados de pacote simples e nenhum hook de lançamento específico do backend. Uma biblioteca pequena ou CLI com uma API pública estreita é uma primeira migração melhor do que um pacote que também compila código nativo ou monta artefatos gerados durante o lançamento.

A fronteira atual importa: uv_build atualmente só oferece suporte a Python puro. Se o próprio pacote cria módulos de extensão, outro backend é necessário. Esse é o limite documentado. uv ainda pode ser valioso como frontend em torno de outro backend, mas a substituição do backend deve esperar até que a paridade dos artefatos seja comprovada. Projetos Python com GPU e próximos de nativo, como as cargas de trabalho discutidas no teste de aceitação de rota NVIDIA Warp, precisam manter essa separação clara. Empacotar um wrapper Python não é o mesmo trabalho que compilar todas as dependências que ele pode chamar.

Formato do projetoDecisão de backendO que provar antes do lançamento
Biblioteca Python pura com layout srcuv_build é um candidato razoávelO wheel importa, o sdist reconstrói, os dados do pacote estão presentes
Pacote CLI simplesuv_build pode servirO console script instalado chama o módulo empacotado
Pacote com arquivos geradosDecida depois que o caminho de geração estiver claroAs saídas geradas estão nos dois artefatos ou são reconstruídas a partir do sdist
Pacote com módulos de extensãoUse outro backendEtapas de build nativo, tags de plataforma e toolchains são tratados
Projeto maduro com setuptools customizado ou HatchlingMantenha primeiro o backend atualO frontend uv funciona sem perder o comportamento de lançamento existente

Exemplo trabalhado: invoice-normalizer

Use um pacote hipotético minúsculo para que as verificações tenham algo concreto para inspecionar. O pacote normaliza dicionários de faturas e envia um mapa de moedas como dados do pacote. Ele também tem um console script, o que dá mais um lugar para erros de empacotamento aparecerem.

pyproject.toml:

[build-system]
requires = ["uv_build>=0.12.22,<0.13"]
build-backend = "uv_build"

[project]
name = "invoice-normalizer"
version = "0.1.0"
description = "Normalize simple invoice payloads."
readme = "README.md"
requires-python = ">=3.11"
dependencies = []

[project.scripts]
invoice-normalizer = "invoice_normalizer:main"

README.md não é opcional neste exemplo, porque pyproject.toml aponta para ele. Crie-o antes de fazer o build:

# invoice-normalizer

Normalize simple invoice payloads for packaging checks.

src/invoice_normalizer/__init__.py:

from importlib.resources import files
import json

def normalize_invoice(payload: dict) -> dict:
    currency = payload.get("currency", "USD").upper()
    data = json.loads(files(__package__).joinpath("data/currencies.json").read_text())
    if currency not in data["supported"]:
        raise ValueError(f"unsupported currency: {currency}")
    return {"invoice_id": str(payload["invoice_id"]), "currency": currency}

def main() -> None:
    print(normalize_invoice({"invoice_id": 123, "currency": "usd"}))

src/invoice_normalizer/data/currencies.json:

{"supported": ["USD", "EUR", "GBP"]}

Este não é um sistema de faturas de produção. Ele é deliberadamente pequeno. Tem uma importação pública, um console script e dados de pacote, que são exatamente as partes que muitas vezes funcionam a partir do checkout enquanto falham a partir do wheel construído.

Faça o build e inspecione pelo nome exato do arquivo

Primeiro faça o build das duas distribuições:

uv build

Depois inspecione os arquivos esperados pelo nome exato. Evite exemplos vagos com curingas na documentação e no CI, porque artefatos antigos podem ficar em dist e fazer uma verificação parecer melhor do que é.

python -m zipfile --list dist/invoice_normalizer-0.1.0-py3-none-any.whl
python -m tarfile --list dist/invoice_normalizer-0.1.0.tar.gz

Procure invoice_normalizer/__init__.py, invoice_normalizer/data/currencies.json, metadados do projeto, entradas de README se usadas, arquivos de licença se declarados e metadados de ponto de entrada de console script. O sinal esperado é que o artefato contenha os mesmos arquivos de que sua API pública precisa depois da instalação. Isso não é uma saída registrada deste fluxo de trabalho.

VerificaçãoComando ou alvo de revisãoSinal esperado
Conteúdo do wheelpython -m zipfile --list ...whlO módulo e o arquivo de dados aparecem no wheel
Conteúdo do sdistpython -m tarfile --list ...tar.gzCódigo-fonte, entradas de metadados e arquivo de dados aparecem no sdist
Instalação limpa do wheelInstale o wheel exato fora do checkoutO caminho de importação vem do pacote instalado, não do repositório
Carregamento de dados do pacoteChame normalize_invoice depois da instalaçãoOs dados JSON estão disponíveis por meio de importlib.resources
Teste rápido da CLIExecute invoice-normalizer instaladoO ponto de entrada resolve para o código empacotado

Reconstrua a partir do sdist

Se você publica um sdist, prove que ele consegue reconstruir o wheel a partir dos arquivos que contém. Isso captura uma classe de erro diferente da inspeção do primeiro wheel. Um README, arquivo gerado ou diretório de dados ausente pode ficar invisível até que um consumidor faça build a partir do código-fonte.

REPO_DIR="$PWD"
SDIST_PATH="$REPO_DIR/dist/invoice_normalizer-0.1.0.tar.gz"
SDIST_CHECK_DIR="$(mktemp -d)"

cd "$SDIST_CHECK_DIR"
python -m tarfile --extract "$SDIST_PATH"
cd invoice_normalizer-0.1.0
uv build --wheel
python -m zipfile --list dist/invoice_normalizer-0.1.0-py3-none-any.whl

O sinal esperado é um wheel reconstruído criado a partir do checkout do sdist, seguido por conteúdo de arquivo que ainda inclui o módulo e os dados do pacote. Trate isso como uma sequência de validação proposta, não como um relatório de que os comandos foram executados aqui.

Instale o wheel fora do checkout

Para verificações de instalação limpa, crie o ambiente fora do repositório, instale o wheel exato por meio de um caminho absoluto e então execute verificações de importação e de dados do pacote ali. Não conte uv run a partir do checkout de código-fonte como prova de empacotamento.

REPO_DIR="$PWD"
WHEEL_PATH="$REPO_DIR/dist/invoice_normalizer-0.1.0-py3-none-any.whl"
WHEEL_CHECK_DIR="$(mktemp -d)"

cd "$WHEEL_CHECK_DIR"
uv venv .venv
. .venv/bin/activate
uv pip install "$WHEEL_PATH"
python -c 'from invoice_normalizer import normalize_invoice; assert normalize_invoice({"invoice_id": 7, "currency": "eur"}) == {"invoice_id": "7", "currency": "EUR"}'
python -c 'from importlib.resources import files; assert files("invoice_normalizer").joinpath("data/currencies.json").is_file()'
invoice-normalizer

A asserção de importação verifica a função pública. A asserção de recursos verifica que o arquivo JSON está empacotado, não apenas presente na árvore de código-fonte. O comando da CLI verifica que os metadados de ponto de entrada resolvem depois da instalação. Novamente, esses são sinais esperados para um mantenedor registrar no CI ou nas notas de lançamento; este artigo não afirma execução local.

O que uv build --no-sources prova

Vale a pena usar uv build --no-sources, mas sua fronteira precisa permanecer exata. Ele desativa tool.uv.sources para a resolução de dependências de build a partir de build-system.requires. Isso pode expor suposições de configuração de fontes em torno dos requisitos de build.

Ele não resolve por si só dependências de runtime. Ele não audita importações de runtime. Ele não revela todo pacote não declarado que seu código importa depois da instalação. Se um módulo importa requests em runtime, mas esquece de declará-lo, --no-sources não é a verificação que prova o erro. Uma instalação limpa seguida por testes rápidos de importação e fluxo de trabalho é o melhor lugar para capturar isso.

Use --no-sources como uma verificação em uma sequência de lançamento, não como o portão de lançamento. Se ela falhar, decida o que o pacote deve ser. Pacotes públicos precisam de requisitos versionados e acessíveis. Pacotes privados precisam de acesso documentado ao índice. Pacotes de workspace apenas internos não devem ser publicados como se consumidores externos pudessem instalá-los. Discussões históricas no GitHub sobre separação do backend uv são contexto prático útil, mas a issue 3957 não deve ser tratada como prova de um bug atual de configuração de fontes.

{
  "checklist": "Outside-Consumer Build Check",
  "minimumSignals": ["wheel built", "sdist built", "archives inspected by exact name", "wheel installed outside checkout", "import and package data verified"],
  "noSourcesBoundary": "Checks build dependency resolution without tool.uv.sources; it is not a runtime dependency audit."
}

Docker e verificações de publicação

Docker ajuda quando você quer uma versão fixada do uv, sincronização repetível de dependências e um ambiente de CI limpo. Ele também pode ocultar erros de empacotamento se a imagem copiar a árvore de código-fonte diretamente ou reutilizar camadas em cache. Mantenha a validação da imagem da aplicação separada da validação da distribuição. Um container que inicia não é prova de que o wheel inclui currencies.json.

Antes de uv publish, confirme o nome do pacote, a versão, o requisito de Python, as dependências, as entradas de README, os metadados de licença e a configuração do índice alvo. Depois da publicação, verifique como consumidor a partir do índice alvo pela versão exata. Sucesso no upload só prova que o registro aceitou os arquivos. Não prova que usuários conseguem importar o pacote, executar a CLI ou carregar dados empacotados.

PUBLISH_CHECK_DIR="$(mktemp -d)"
TARGET_INDEX_URL="https://test.pypi.org/simple"

cd "$PUBLISH_CHECK_DIR"
uv venv .venv
. .venv/bin/activate
uv pip install --index-url "$TARGET_INDEX_URL" "invoice-normalizer==0.1.0"
python -c 'from invoice_normalizer import normalize_invoice; assert normalize_invoice({"invoice_id": 42}) == {"invoice_id": "42", "currency": "USD"}'
python -c 'from importlib.resources import files; assert files("invoice_normalizer").joinpath("data/currencies.json").is_file()'
invoice-normalizer

Para um lançamento real, aponte TARGET_INDEX_URL para o índice no qual você realmente publicou e adicione explicitamente qualquer autenticação exigida ou configuração de extra-index. O sinal esperado é uma instalação limpa pela versão exata a partir desse índice, seguida pelas mesmas verificações de importação, dados e CLI usadas antes da publicação.

Erros comuns

O primeiro erro é tratar sucesso do lock como sucesso de publicação. Um lockfile é evidência de contexto do projeto, não evidência de distribuição. Outro erro comum é trocar backend, layout de dependências, CI e credenciais de publicação em uma única alteração. Isso torna falhas mais difíceis de interpretar. Mude uma camada por vez.

Um terceiro erro é pular o sdist porque o wheel instalou localmente. Alguns consumidores ainda fazem build a partir do código-fonte, e um sdist quebrado pode se tornar a primeira falha que eles veem. Um quarto é presumir que Docker respondeu à pergunta sobre o artefato. Ele não respondeu, a menos que o container tenha instalado o mesmo wheel ou sdist que um consumidor receberá.

O melhor caminho de adoção é quase monótono, o que é um elogio em engenharia de lançamento: use uv primeiro como frontend, mantenha o backend atual se ele já codifica o comportamento de lançamento, e adote uv_build apenas quando o formato do projeto se encaixar. Trate o wheel e o sdist como o produto. Valide-os de fora do checkout. Escolha o backend depois que a evidência dos artefatos o sustentar.

Pontos principais

  • 1uv simplifica fluxos de build e publicação de pacotes Python, mas os artefatos ainda precisam de validação como consumidor externo.
  • 2uv_build atualmente só oferece suporte a projetos Python puros; módulos de extensão exigem outro backend.
  • 3uv build --no-sources verifica a resolução de dependências de build sem tool.uv.sources, não a completude de dependências de runtime.
  • 4A instalação limpa a partir de artefatos construídos fora do checkout é o sinal local mais forte antes da publicação.
  • 5A repetibilidade do Docker não prova a correção do wheel ou do sdist.

Conclusão

uv está no seu melhor quando torna o empacotamento mais fácil de raciocinar, não quando oculta suposições de lançamento por trás de um comando mais rápido. Trate o wheel e o sdist como o produto, valide-os de fora do checkout e escolha uv_build apenas quando o pacote for Python puro e os artefatos construídos comprovarem a história de lançamento.

Perguntas frequentes

O que uv build cria?

uv build cria artefatos de distribuição Python, como wheels e distribuições de código-fonte, dependendo do projeto e do comando usado.

Quando devo usar o backend de build do uv?

Use uv_build para pacotes Python puros compatíveis com layouts convencionais. Use outro backend quando o pacote criar módulos de extensão ou precisar de comportamento customizado de backend.

Para que serve uv build --no-sources?

Ele desativa tool.uv.sources para a resolução de dependências de build a partir de build-system.requires, ajudando a expor suposições de fontes de build. Não é uma auditoria de dependências de runtime.

Uma imagem Docker funcional prova que meu pacote Python está correto?

Não. Docker pode copiar arquivos de código-fonte ou usar camadas em cache que ocultam conteúdo ausente do wheel ou do sdist.

O que devo verificar depois da publicação?

Instale a versão publicada a partir do índice alvo em um ambiente limpo e então verifique importações, console scripts, dados de pacote e um fluxo de trabalho mínimo de consumidor.

Fontes

Compartilhar este artigo

Hamza Diaz

Escrito por

Hamza Diaz

Hamza Diaz é o fundador da Optijara, onde cria agentes de IA práticos, sistemas de automação e fluxos de trabalho do Copilot para empresas de serviços. Ele escreve sobre operações de IA, estratégia de agentes e implementação no mundo real para equipes que querem sistemas úteis em vez de exagero.