Distribuição de Pacotes e Publicação no PyPI
Construa, empacote e distribua projetos Python com pyproject.toml, setuptools, wheels, versionamento e publicação no PyPI
Distribuição de Pacotes e Publicação no PyPI
Por que Empacotar Seu Código?
O empacotamento permite instalação via pip, declara dependências, fornece pontos de entrada e torna seu código reutilizável entre projetos.
pyproject.toml (Padrão Moderno)
pyproject.toml é o padrão moderno de empacotamento Python (PEP 517/518/621). Ele substitui setup.py e setup.cfg.
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-awesome-lib"
version = "0.1.0"
description = "Uma descrição curta da minha biblioteca"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
keywords = ["python", "exemplo", "tutorial"]
authors = [
{name = "Seu Nome", email="voce@exemplo.com"},
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"requests>=2.28",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-cov>=4.0",
"black>=23.0",
"ruff>=0.1",
"mypy>=1.0",
]
test = ["pytest>=7.0", "httpx>=0.24"]
docs = ["mkdocs>=1.4", "mkdocstrings"]
[project.urls]
Homepage = "https://github.com/voce/my-awesome-lib"
Documentation = "https://my-awesome-lib.readthedocs.io"
Repository = "https://github.com/voce/my-awesome-lib"
Issues = "https://github.com/voce/my-awesome-lib/issues"
[project.scripts]
my-cli = "my_awesome_lib.cli:main"
[project.gui-scripts]
my-gui = "my_awesome_lib.gui:launch"
[tool.setuptools.packages.find]
where = ["src"]
include = ["my_awesome_lib*"]
exclude = ["tests*", "docs*"]A seção [project.scripts] cria pontos de entrada de console. Quando os usuários instalarem seu pacote com pip install, esses se tornarão comandos executáveis no PATH.
Estrutura do Projeto
my-awesome-lib/
├── pyproject.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
├── src/
│ └── my_awesome_lib/
│ ├── __init__.py
│ ├── cli.py
│ ├── core.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ ├── test_core.py
│ └── test_cli.py
└── docs/
├── index.md
└── api.md
Usando o Layout src/
O layout src/ previne confusão de importações durante o desenvolvimento e teste.
# src/my_awesome_lib/core.py
def add(a, b):
"""Soma dois números."""
return a + b
# src/my_awesome_lib/cli.py
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("numbers", nargs=2, type=float)
args = parser.parse_args()
result = add(args.numbers[0], args.numbers[1])
print(f"Resultado: {result}")
# src/my_awesome_lib/__init__.py
from .core import addConstruindo Wheels
# Instalar ferramentas de build
pip install build twine
# Construir distribuição fonte e wheel
python -m build
# Verificar o wheel construído
ls dist/
# my_awesome_lib-0.1.0.tar.gz
# my_awesome_lib-0.1.0-py3-none-any.whlWheels (.whl) são o formato de distribuição preferido. Eles instalam mais rápido que distribuições fonte porque pulam a etapa de build.
Enviando para o PyPI
# Enviar para o TestPyPI primeiro
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
# Enviar para o PyPI de produção
twine upload dist/*
# Instalar a partir do TestPyPI
pip install --index-url https://test.pypi.org/simple/ my-awesome-libUsando ~/.pypirc
[distutils]
index-servers =
pypi
testpypi
[pypi]
username = __token__
password = pypi-xxxxx...
[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-xxxxx...Nunca commite seu token ou senha do PyPI. Use variáveis de ambiente (TWINE_USERNAME, TWINE_PASSWORD) ou keyring em CI/CD.
Versionamento
Versionamento Semântico (SemVer)
MAJOR.MINOR.PATCH
MAJOR: Mudanças de API incompatíveis
MINOR: Novas funcionalidades compatíveis com versões anteriores
PATCH: Correções de bugs compatíveis com versões anteriores
# __version__.py — fonte única de verdade
__version__ = "0.1.0"Versionamento Dinâmico com setuptools-scm
[build-system]
requires = ["setuptools>=68.0", "wheel", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-awesome-lib"
dynamic = ["version"]
[tool.setuptools_scm]
version_scheme = "post-release"A versão é derivada de tags git (git tag v0.1.0).
Fluxo de Publicação (CI/CD)
# .github/workflows/publish.yml
name: Publicar no PyPI
on:
release:
types: [published]
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install build twine
- run: python -m build
- run: twine upload dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}Use Publicação Confiável (OIDC) com PyPI para a configuração de CI/CD mais segura. Não requer tokens armazenados.
Arquivos Manifest
# MANIFEST.in — incluir arquivos extras na distribuição fonte
include README.md
include LICENSE
include CHANGELOG.md
recursive-include src/my_awesome_lib/data *Checklist Completo do Pacote
Exemplo Real: Publicando uma Ferramenta CLI
# src/mycalc/cli.py
import argparse
from .core import calculate
def main():
parser = argparse.ArgumentParser(description="Uma calculadora simples")
parser.add_argument("expression", help="Expressão matemática para avaliar")
args = parser.parse_args()
result = calculate(args.expression)
print(f"= {result}")
if __name__ == "__main__":
main()[project.scripts]
mycalc = "mycalc.cli:main"Após pip install mycalc:
mycalc "2 + 2"
# = 4Questões de Prática
- O que é o arquivo
pyproject.tomle por que ele é preferido em relação aosetup.py? - Crie um
pyproject.tomlpara um pacote chamadotextutilscom dependências emclickepyyaml. - Qual é a diferença entre uma distribuição fonte (
.tar.gz) e um wheel (.whl)? Quando usar cada um? - Escreva um fluxo de trabalho do GitHub Actions que publica um pacote no PyPI quando um release é criado.
- Como o
setuptools-scmderiva a versão do pacote a partir do git? Quais são os benefícios? - O que é o layout
src/e por que ele é recomendado para pacotes Python? - Construa uma ferramenta CLI simples com pontos de entrada em
pyproject.tomle publique-a no TestPyPI. - Como lidar com dependências opcionais em
pyproject.toml? Dê um exemplo com extrasdevetest. - O que é Publicação Confiável (OIDC) no PyPI e como ela melhora a segurança?
- Crie uma estrutura de projeto completa para um pacote chamado
csvprocque processa arquivos CSV com testes, documentação e empacotamento adequados.