Criando e publicando um pacote no PyPI

1. Preparação do ambiente e estrutura do projeto

1.1. Ferramentas essenciais

Antes de começar, certifique-se de ter Python 3.8+ instalado. As ferramentas fundamentais são:

python -m pip install --upgrade pip
pip install build twine virtualenv

Crie um ambiente virtual para isolar seu projeto:

python -m venv venv
source venv/bin/activate  # Linux/Mac
venv\Scripts\activate     # Windows

1.2. Estrutura de diretórios recomendada

Adotaremos o src layout (recomendado pela comunidade):

meu-pacote/
├── src/
│   └── meupacote/
│       ├── __init__.py
│       ├── modulo1.py
│       └── utils.py
├── tests/
│   ├── __init__.py
│   └── test_modulo1.py
├── pyproject.toml
├── README.md
├── CHANGELOG.md
├── LICENSE
└── .gitignore

1.3. Versionamento com Git

git init
git add .
git commit -m "Initial commit: estrutura básica do pacote"

Adicione ao .gitignore:

venv/
__pycache__/
*.pyc
dist/
*.egg-info/
*.whl

2. Configuração do pyproject.toml (PEP 621)

O pyproject.toml é o coração da configuração moderna. Exemplo completo:

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "meupacote"
version = "0.1.0"
description = "Um pacote Python incrível para demonstração"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
    {name = "Seu Nome", email = "seu@email.com"}
]

dependencies = [
    "requests>=2.25.0",
    "click>=8.0.0"
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black",
    "flake8"
]

[project.urls]
Homepage = "https://github.com/seuusuario/meu-pacote"
Repository = "https://github.com/seuusuario/meu-pacote.git"
Documentation = "https://meupacote.readthedocs.io"

[project.scripts]
meu-cli = "meupacote.modulo1:main"

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

2.1. Classificadores (Trove Classifiers)

Adicione classificadores relevantes:

[project.classifiers]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Topic :: Software Development :: Libraries :: Python Modules",
]

3. Escrita do código do pacote

3.1. Organização de módulos

Crie src/meupacote/__init__.py:

"""Pacote de demonstração para PyPI."""

from .modulo1 import saudacao
from .utils import processar_dados

__version__ = "0.1.0"
__author__ = "Seu Nome"

src/meupacote/modulo1.py:

"""Módulo principal com funcionalidades do pacote."""

def saudacao(nome: str) -> str:
    """Retorna uma saudação personalizada."""
    return f"Olá, {nome}! Bem-vindo ao meupacote."

def main():
    """Entry point para CLI."""
    import sys
    nome = sys.argv[1] if len(sys.argv) > 1 else "Mundo"
    print(saudacao(nome))

src/meupacote/utils.py:

"""Utilitários para processamento de dados."""

def processar_dados(numeros: list) -> dict:
    """Processa uma lista de números e retorna estatísticas."""
    return {
        "soma": sum(numeros),
        "media": sum(numeros) / len(numeros),
        "maximo": max(numeros),
        "minimo": min(numeros)
    }

3.2. Importação relativa vs absoluta

Use importação absoluta dentro do pacote:

# Correto (absoluto)
from meupacote.utils import processar_dados

# Evite (relativo)
from .utils import processar_dados  # Funciona, mas menos explícito

3.3. Inclusão de dados estáticos

Crie MANIFEST.in para incluir arquivos adicionais:

include README.md
include CHANGELOG.md
include LICENSE
include pyproject.toml
recursive-include src/meupacote/data *

4. Geração de builds e testes locais

4.1. Construção do pacote

python -m build

Isso gera:
- dist/meupacote-0.1.0-py3-none-any.whl (wheel)
- dist/meupacote-0.1.0.tar.gz (sdist)

4.2. Instalação local para desenvolvimento

pip install -e .

Agora você pode importar e testar:

from meupacote import saudacao, processar_dados

print(saudacao("Python"))  # Olá, Python! Bem-vindo ao meupacote.
print(processar_dados([1, 2, 3, 4, 5]))
# {'soma': 15, 'media': 3.0, 'maximo': 5, 'minimo': 1}

4.3. Verificação com twine

twine check dist/*

Deve retornar "Passed" sem erros.

5. Versionamento semântico e changelog

5.1. Semantic Versioning (SemVer)

Siga o formato MAJOR.MINOR.PATCH:
- MAJOR: mudanças incompatíveis na API
- MINOR: novas funcionalidades compatíveis
- PATCH: correções de bugs

5.2. Ferramentas de versionamento

Com bumpversion:

pip install bumpversion
bumpversion patch  # 0.1.0 → 0.1.1
bumpversion minor  # 0.1.1 → 0.2.0
bumpversion major  # 0.2.0 → 1.0.0

Com setuptools-scm (automático via git):

No pyproject.toml:

[project]
dynamic = ["version"]

[tool.setuptools_scm]
write_to = "src/meupacote/_version.py"
pip install setuptools-scm

5.3. Manutenção de CHANGELOG.md

# Changelog

## [0.1.1] - 2025-01-15
### Fixed
- Correção na função `processar_dados` para listas vazias

## [0.1.0] - 2025-01-10
### Added
- Função `saudacao` para saudações personalizadas
- Função `processar_dados` para estatísticas
- CLI básica com entry point

6. Publicação no PyPI

6.1. Criação de contas

  1. TestPyPI — para testes
  2. PyPI oficial — para publicação real

6.2. Autenticação com tokens de API

Gere tokens em: Account Settings → API tokens

Configure localmente:

# Para TestPyPI
cat > ~/.pypirc << EOF
[testpypi]
username = __token__
password = pypi-xxxxxxxxxxxxxxxxxxxx
EOF

# Para PyPI oficial
[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username = __token__
password = pypi-yyyyyyyyyyyyyyyyyyyy

6.3. Upload com twine

Primeiro para TestPyPI:

twine upload --repository testpypi dist/*

Teste a instalação:

pip install --index-url https://test.pypi.org/simple/ meupacote

Depois para PyPI oficial:

twine upload dist/*

7. Automação e CI/CD

7.1. Workflow GitHub Actions

Crie .github/workflows/publish.yml:

name: Publicar no PyPI

on:
  release:
    types: [published]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      - name: Instalar dependências
        run: |
          python -m pip install --upgrade pip
          pip install pytest
          pip install -e .
      - name: Executar testes
        run: pytest

  publish:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      - name: Instalar dependências
        run: |
          python -m pip install --upgrade pip
          pip install build twine
      - name: Build
        run: python -m build
      - name: Publicar no PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          password: ${{ secrets.PYPI_API_TOKEN }}

7.2. Configuração de segredos

No GitHub: Settings → Secrets and variables → Actions → New repository secret

Adicione PYPI_API_TOKEN com seu token do PyPI.

7.3. Publicação automática

Ao criar uma release no GitHub, o workflow:
1. Executa testes
2. Constrói o pacote
3. Publica automaticamente no PyPI

8. Manutenção e boas práticas pós-publicação

8.1. Atualização de versões

# Para correções rápidas
bumpversion patch
git push --tags
# Crie uma nova release no GitHub

8.2. Remoção ou deprecação (yanking)

Se uma versão tiver problemas críticos:

pip install yank
yank meupacote==0.1.0

Ou manualmente no site do PyPI: "Yank this release".

8.3. Monitoramento

Acompanhe métricas no PyPI Stats:

pip install pypistats
pypistats recent meupacote

Referências