Pacote Python: O que É e Como Funciona | Python Brasil
Guia completo sobre pacotes Python: __init__.py, pyproject.toml, build, publicação no PyPI, versionamento semântico e registros privados.
O que é um Pacote Python?
Um pacote (package) em Python é uma forma de organizar módulos relacionados em uma estrutura de diretórios hierárquica. Na definição clássica, um pacote é um diretório que contém um arquivo __init__.py e um ou mais módulos (arquivos .py). O __init__.py sinaliza ao Python que aquele diretório deve ser tratado como um pacote e pode conter código de inicialização.
Pacotes permitem criar namespaces hierárquicos: você pode ter meu_app.models.usuario e meu_app.models.produto sem conflito de nomes.
A evolução do init.py
O __init__.py passou por mudanças importantes ao longo das versões do Python:
# Antes do Python 3.3: __init__.py era OBRIGATÓRIO
# meu_pacote/__init__.py precisava existir (mesmo vazio)
# Python 3.3+ introduziu "namespace packages" (PEP 420)
# Diretórios SEM __init__.py também podem ser importados
# Isso permite pacotes espalhados por múltiplos diretórios
# Quando usar __init__.py moderno:
# meu_pacote/__init__.py
# Expor a API pública do pacote
from .core import funcao_principal
from .utils import formatar, validar
from .models import Usuario, Produto
# Versão do pacote (acessível como meu_pacote.__version__)
__version__ = "2.1.0"
# Controlar o que é exportado com import *
__all__ = ["funcao_principal", "formatar", "Usuario", "Produto"]
Para pacotes de distribuição (que serão instalados via pip), sempre inclua o __init__.py. Para namespaces internos grandes em aplicações, pacotes implícitos podem simplificar a estrutura.
Estrutura de um pacote completo
meu_pacote/
├── src/
│ └── meu_pacote/
│ ├── __init__.py
│ ├── core.py
│ ├── utils.py
│ └── models/
│ ├── __init__.py
│ ├── usuario.py
│ └── produto.py
├── tests/
│ ├── __init__.py
│ ├── test_core.py
│ └── test_models.py
├── docs/
├── pyproject.toml
├── README.md
└── LICENSE
O layout src/ (conhecido como “src layout”) é a convenção recomendada atualmente, pois evita que o Python importe acidentalmente o código do diretório de trabalho em vez do pacote instalado.
pyproject.toml: o arquivo de configuração moderno
O pyproject.toml é o padrão atual (PEP 517, PEP 518, PEP 621) para configurar projetos e pacotes Python. Ele substituiu o setup.py e o setup.cfg:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "meu-pacote"
version = "1.0.0"
description = "Uma biblioteca Python para processamento de dados"
readme = "README.md"
license = { file = "LICENSE" }
authors = [
{ name = "Seu Nome", email = "voce@exemplo.com" }
]
keywords = ["dados", "processamento", "python"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
requires-python = ">=3.9"
dependencies = [
"requests>=2.28.0",
"pydantic>=2.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"black>=23.0",
"mypy>=1.0",
]
[project.urls]
Homepage = "https://github.com/usuario/meu-pacote"
Documentation = "https://meu-pacote.readthedocs.io"
Repository = "https://github.com/usuario/meu-pacote.git"
[project.scripts]
meu-comando = "meu_pacote.cli:main"
setup.py vs setup.cfg vs pyproject.toml
A evolução das ferramentas de empacotamento Python:
# setup.py — legado, mas ainda encontrado em projetos antigos
from setuptools import setup, find_packages
setup(
name="meu-pacote",
version="1.0.0",
packages=find_packages(where="src"),
package_dir={"": "src"},
install_requires=["requests>=2.28.0"],
)
# setup.cfg — configuração declarativa (intermediário)
# [metadata]
# name = meu-pacote
# version = 1.0.0
#
# [options]
# install_requires =
# requests>=2.28.0
# pyproject.toml — padrão atual recomendado
# (veja exemplo completo acima)
Para projetos novos, use exclusivamente pyproject.toml. Projetos legados com setup.py funcionam, mas a migração é recomendada.
Criando e usando o pacote localmente
# meu_pacote/core.py
def processar(dados: list) -> dict:
"""Processa uma lista de dados e retorna estatísticas."""
if not dados:
return {"total": 0, "media": 0}
return {
"total": len(dados),
"soma": sum(dados),
"media": sum(dados) / len(dados),
"minimo": min(dados),
"maximo": max(dados),
}
# meu_pacote/models/usuario.py
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class Usuario:
nome: str
email: str
criado_em: datetime = field(default_factory=datetime.now)
def __post_init__(self):
if "@" not in self.email:
raise ValueError(f"Email inválido: {self.email}")
# Instalar o pacote local em modo editável
# pip install -e .
# Usar o pacote
from meu_pacote import processar
from meu_pacote.models.usuario import Usuario
resultado = processar([10, 20, 30, 40, 50])
print(resultado) # {'total': 5, 'soma': 150, 'media': 30.0, ...}
user = Usuario("Ana", "ana@exemplo.com")
print(user) # Usuario(nome='Ana', email='ana@exemplo.com', ...)
Construindo o pacote para distribuição
Para gerar os arquivos de distribuição (wheel e sdist), use a ferramenta build:
# Instalar a ferramenta de build
pip install build
# Construir o pacote
python -m build
# Gera dois arquivos em dist/:
# dist/meu_pacote-1.0.0-py3-none-any.whl (wheel — formato moderno)
# dist/meu_pacote-1.0.0.tar.gz (sdist — código fonte)
Ferramentas alternativas de build com funcionalidades extras:
# Flit — simples, focado em pacotes Python puro
pip install flit
flit build
# Hatch — moderno, com gerenciamento de versões e ambientes
pip install hatch
hatch build
# Poetry — all-in-one: dependências + build + publish
poetry build
Publicando no PyPI passo a passo
# 1. Criar conta em pypi.org e test.pypi.org
# 2. Instalar twine (ferramenta de upload)
pip install twine
# 3. Testar primeiro no TestPyPI
twine upload --repository testpypi dist/*
# Instalar do TestPyPI para validar:
pip install --index-url https://test.pypi.org/simple/ meu-pacote
# 4. Publicar no PyPI oficial
twine upload dist/*
# Informe seu token de API do PyPI (recomendado sobre senha)
# 5. Qualquer pessoa pode instalar
pip install meu-pacote
Configure o token de API no ~/.pypirc para não digitar toda vez:
[pypi]
username = __token__
password = pypi-AgENdGVzdC5weXBpLm9yZwIkMWI...
Registros privados de pacotes
Em ambientes corporativos, você pode hospedar pacotes internos em registros privados:
# Instalar do registro privado
pip install meu-pacote-interno \
--extra-index-url https://usuario:senha@registry.empresa.com/simple/
# Publicar no registro privado com twine
twine upload \
--repository-url https://registry.empresa.com/ \
dist/*
# pip.conf para configurar o registro permanentemente
# [global]
# extra-index-url = https://token@registry.empresa.com/simple/
Soluções populares para registros privados: Artifactory (JFrog), Nexus (Sonatype), AWS CodeArtifact, Google Artifact Registry e o devpi (open source).
Versionamento semântico
Todo pacote publicado deve seguir o SemVer (Versionamento Semântico):
MAJOR.MINOR.PATCH
2 . 1 . 3
MAJOR — mudanças incompatíveis com versões anteriores (breaking changes)
MINOR — novas funcionalidades compatíveis com versões anteriores
PATCH — correções de bugs compatíveis com versões anteriores
Exemplos:
1.0.0 → 1.0.1 (correção de bug)
1.0.1 → 1.1.0 (nova feature)
1.1.0 → 2.0.0 (breaking change)
Versões de pré-lançamento:
2.0.0-alpha.1
2.0.0-beta.1
2.0.0rc1 (release candidate — convenção do PyPI)
Pacotes vs Módulos
A diferença é objetiva: um módulo é um único arquivo .py, enquanto um pacote é um diretório com múltiplos módulos. Pacotes permitem organizar projetos grandes de forma hierárquica. Na prática, quando você instala algo via pip install, está instalando um pacote de distribuição (distribution package) — que pode conter múltiplos pacotes e módulos Python.