Curso de Python / Empacotar um projeto e verificar a instalação do wheel

Um projeto Python pode declarar sua configuração em pyproject.toml e gerar arquivos de distribuição, como um wheel. Instale o wheel em um ambiente virtual limpo e execute os testes fora da pasta de fontes para verificar se o pacote realmente foi incluído e instalado.

Separar o nome da distribuição e o pacote importado

Neste exercício, o nome usado na instalação é expensekit-demo, enquanto o pacote importado é expensekit. São identificadores com funções diferentes: um nome de distribuição pode conter hífen, mas esse não é o nome escrito depois de import.

O layout src mantém o código importável dentro de uma subpasta. Isso reduz o risco de um teste funcionar apenas porque o diretório atual contém as fontes, escondendo uma instalação incompleta. O teste final ainda precisa usar outro diretório e o interpretador correto. A comparação oficial entre os layouts src e plano explica essa separação.

Use Python 3.12 ou posterior, conforme requires-python do exemplo. Crie uma pasta nova para o projeto e um ambiente de construção com as práticas da aula de venv e pip. O projeto não tem dependências de execução externas; a ferramenta build e o backend setuptools participam da construção.

Os nomes deste exemplo servem para o teste local. A criação dos arquivos não verifica disponibilidade nem propriedade de nomes num repositório público. O procedimento a seguir constrói e instala localmente.

Criar todos os arquivos do projeto

Na pasta do projeto, crie src/expensekit. Salve os três arquivos abaixo em UTF-8. Conteúdo completo de src/expensekit/__init__.py:

def total(amounts):
    if any(type(x) is not int or x < 0 for x in amounts):
        raise ValueError("invalid amount")
    return sum(amounts)

A função aceita listas ou tuplas reutilizáveis de inteiros não negativos; rejeita booleanos, strings e números negativos. A validação percorre os valores antes da soma. Por isso, este código não é adequado a um iterador consumível, como iter([1, 2, 3]): ele pode ser esgotado na validação e produzir soma zero. Quando a entrada vier de um gerador, materialize-a em uma lista antes de chamar a função ou reprojete a validação em uma única passagem.

Conteúdo completo de src/expensekit/cli.py:

import argparse
from . import total

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("amounts", type=int, nargs="+")
    args = parser.parse_args()
    print(total(args.amounts))

if __name__ == "__main__":
    main()

O parser converte os argumentos em inteiros e exige pelo menos um valor. A validação de domínio fica na função total. A proteção do ponto de entrada evita processar argumentos ao importar o módulo, como explica a aula sobre pacotes e pontos de entrada.

Na raiz do projeto, ao lado de src, salve este conteúdo completo em pyproject.toml:

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "expensekit-demo"
version = "0.1.0"
description = "Local expense sum example"
requires-python = ">=3.12"

[project.scripts]
expense-total = "expensekit.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

build-system escolhe o backend e seus requisitos; project contém os metadados da distribuição. Em project.scripts, expense-total aponta para a função main de expensekit.cli. A busca de pacotes usa a pasta src. Não salve o arquivo como pyproject.toml.txt.

Construir e instalar em um ambiente limpo

No terminal do ambiente de construção, posicionado na pasta que contém pyproject.toml, execute:

python -m pip install build
python -m build

O primeiro comando instala a ferramenta no ambiente selecionado. O segundo usa o backend para gerar o wheel e uma distribuição de fontes, ou sdist. A construção isolada pode precisar baixar os requisitos declarados; falhas de acesso ao índice ou de compatibilidade devem ser resolvidas antes de prosseguir. A orientação oficial de empacotamento descreve o fluxo e os arquivos envolvidos.

Confira se apareceram em dist os arquivos expensekit_demo-0.1.0-py3-none-any.whl e expensekit_demo-0.1.0.tar.gz. O nome de arquivo normaliza o hífen da distribuição para sublinhado. Crie agora um ambiente de verificação novo, sem reutilizar o ambiente de desenvolvimento:

python -m venv .venv-check

No Windows PowerShell, instale o wheel com o interpretador desse ambiente:

.\.venv-check\Scripts\python.exe -m pip install dist/expensekit_demo-0.1.0-py3-none-any.whl

No macOS ou Ubuntu, o caminho correspondente é:

.venv-check/bin/python -m pip install dist/expensekit_demo-0.1.0-py3-none-any.whl

Não execute as duas variantes: escolha a do seu sistema. Se a pasta .venv-check já existir e puder conter uma instalação anterior, escolha outro nome novo e ajuste todos os caminhos. Os ambientes virtuais não são arquivos a incluir na distribuição.

Para os testes, guarde o caminho absoluto de .venv-check e vá para uma pasta nova fora do projeto. Ative esse ambiente ou substitua python pelo caminho absoluto de seu executável: Scripts/python.exe no Windows e bin/python no macOS ou Ubuntu. Um caminho relativo à pasta antiga deixará de funcionar depois da mudança de diretório. Execute:

python -c "from expensekit import total; print(total([3500,8000,2500]))"
python -m expensekit.cli 3500 8000 2500

Ambos os comandos devem imprimir 14000. No ambiente ativado, expense-total 3500 8000 2500 deve apresentar o mesmo resultado. Sem ativação, use o caminho absoluto de Scripts/expense-total.exe no Windows ou bin/expense-total nos outros sistemas. O instalador cria esse comando a partir de project.scripts; confira o comportamento na documentação dos pontos de entrada do setuptools.

Troque somente 8000 por 9000 na chamada: espere 15000. Restaure o valor e confirme 14000. Com abc, o parser deve rejeitar a entrada; com -1, a função deve levantar ValueError: invalid amount. O exemplo deixa esse erro de domínio aparecer como traceback, em vez de oferecer uma mensagem personalizada. Uma interface destinada a usuários finais pode tratá-lo explicitamente.

Diagnosticar a instalação e preparar uma versão pública

A estrutura resultante é:

project/
  pyproject.toml
  src/
    expensekit/
      __init__.py
      cli.py
  dist/
    expensekit_demo-0.1.0-py3-none-any.whl
    expensekit_demo-0.1.0.tar.gz

Se surgir ModuleNotFoundError fora da pasta de fontes, confirme o interpretador com python -c "import sys; print(sys.executable)" e consulte python -m pip show expensekit-demo nesse mesmo ambiente. Verifique a presença dos dois módulos no wheel e a configuração de descoberta em src. Não acrescente a pasta de fontes a PYTHONPATH apenas para fazer o teste passar: isso ocultaria o problema de instalação.

Se o import e a execução por módulo funcionarem, mas expense-total não for encontrado, confira a ativação ou o caminho do lançador. Se importar expensekit.cli iniciar o parser, reveja a proteção __main__. Se alterar fontes depois de construir, será necessário construir e instalar novamente para testar o novo artefato; uma instalação comum do wheel não acompanha automaticamente essas mudanças.

Declare bibliotecas necessárias em execução em project.dependencies e verifique a compatibilidade com as versões de Python anunciadas. Ferramentas usadas apenas para testes ou construção não entram automaticamente nessa lista.

Uma publicação pública também exige verificar o nome no repositório, licença, autoria verdadeira, README, arquivos incluídos, ausência de segredos e política de lançamento e autenticação. O teste desta aula termina com os arquivos locais e a instalação verificada; disponibilizar uma versão no PyPI é uma etapa distinta.