Formatos de Pacotes#

Esta página discute os formatos de arquivo usados para distribuir pacotes Python e as diferenças entre eles.

Você encontrará arquivos em dois formatos em índices de pacotes como PyPI: distribuições fonte, ou a abreviação sdists, e distribuições binárias, comumente chamadas de wheels. Por exemplo, a página PyPI para pip 23.3.1 permite baixar dois arquivos, pip-23.3.1.tar.gz e pip-23.3.1-py3-none-any.whl. O primeiro é um sdist, o último é uma wheel. Conforme explicado abaixo, eles têm finalidades diferentes. Ao publicar um pacote no PyPI (ou em outro lugar), você deve sempre enviar um sdist e um ou mais wheel.

O que é uma distribuição fonte?#

Conceitualmente, uma distribuição fonte é um arquivo do código-fonte em formato bruto. Concretamente, um sdist é um arquivo .tar.gz contendo o código-fonte mais um arquivo especial adicional chamado PKG-INFO, que contém os metadados do projeto. A presença desse arquivo ajuda as ferramentas de empacotamento a serem mais eficientes, pois não precisam calcular os próprios metadados. O arquivo PKG-INFO segue o formato especificado em Especificações de metadados principais e não se destina a ser escrito à mão [1].

Você pode, portanto, operar o conteúdo de um sdist descompactando-o usando ferramentas padrão para trabalhar com arquivos tar, como tar -xvf em plataformas UNIX (como Linux e macOS), ou a interface de linha de comando do módulo tarfile do Python em qualquer plataforma.

Os sdists servem a vários propósitos no ecossistema de embalagens. Quando pip, o instalador padrão do pacote Python, não consegue encontrar um wheel para instalar, ele recorrerá ao download de uma distribuição fonte, compilando um wheel a partir dela e instalando o wheel. Além disso, os sdists são frequentemente usados como fonte de pacote por empacotadores downstream (como distribuições Linux, Conda, Homebrew e MacPorts no macOS, …), que, por vários motivos, podem preferi-los, por exemplo, extraindo de um repositório Git.

Uma distribuição fonte é reconhecida pelo seu nome de arquivo, que tem o formato nome_do_pacote-versão.tar.gz, por exemplo, pip-23.3.1.tar.gz.

Se você quiser detalhes técnicos sobre o formato sdist, leia a especificação de sdist.

O que é um wheel?#

Conceitualmente, um wheel contém exatamente os arquivos que precisam ser copiados durante a instalação do pacote.

Há uma grande diferença entre sdists e wheels para pacotes com módulos de extensão, escritos em linguagens compiladas como C, C++ e Rust, que precisam ser compilados em código de máquina dependente de plataforma. Com esses pacotes, as rodas não contêm código fonte (como arquivos fonte C), mas código executável compilado (como arquivos .so no Linux ou DLLs no Windows).

Além disso, embora exista apenas um sdist por versão de um projeto, pode haver muitos wheels. Novamente, isto é mais relevante no contexto dos módulos de extensão. O código compilado de um módulo de extensão está vinculado a um sistema operacional e arquitetura de processador, e muitas vezes também à versão do interpretador Python (a menos que a ABI estável do Python seja usada).

Para pacotes Python puro, a diferença entre sdists e wheels é menos acentuada. Normalmente existe um único wheel, para todas as plataformas e versões do Python. Python é uma linguagem interpretada, que não precisa de compilação antecipada, então wheels contêm arquivos .py assim como sdists.

Se você está se perguntando sobre os arquivos de bytecode .pyc: eles não estão incluídos nos wheels, pois são pouco custoso para gerar, e incluí-los forçaria desnecessariamente um grande número de pacotes a distribuir um wheel por versão do Python em vez de um único wheel. Em vez disso, instaladores como pip os geram durante a instalação do pacote.

Dito isto, ainda existem diferenças importantes entre sdists e wheels, mesmo para projetos Python puros. Os wheels devem conter exatamente o que será instalado e nada mais. Em particular, os wheels nunca devem incluir testes e documentação, ao contrário dos sdists. Além disso, o formato do wheel é mais complexo que o sdist. Por exemplo, inclui um arquivo especial – chamado RECORD – que lista todos os arquivos no wheel junto com um hash de seu conteúdo, como uma verificação de segurança da integridade do download.

À primeira vista, você pode se perguntar se wheels são realmente necessários para projetos “simples e básicos” de Python puro. Tenha em mente que devido à flexibilidade dos sdists, instaladores como o pip não podem instalar diretamente a partir dos sdists – eles precisam primeiro construir um wheel, invocando o backend de construção que o sdist especifica (o backend de construção pode fazer todo tipo de transformações durante a construção do wheel, como compilar extensões C). Por esse motivo, mesmo para um projeto Python puro, você deve sempre enviar ambos sdist e wheel para o PyPI ou outros índices de pacotes. Isso torna a instalação muito mais rápida para seus usuários, já que um wheel pode ser instalado diretamente. Ao incluir apenas os arquivos que devem ser instalados, os wheels também permitem downloads menores.

No nível técnico, um wheel é um arquivo ZIP (ao contrário dos sdists que são arquivos TAR). Você pode inspecionar seu conteúdo descompactando-o como um arquivo ZIP normal, por exemplo, usando unzip em plataformas UNIX como Linux e macOS, Expand-Archive no PowerShell no Windows, ou a linha de comando interface do módulo zipfile do Python. Isso pode ser muito útil para verificar se o wheel inclui todos os arquivos necessários.

Dentro de um wheel, você encontrará os arquivos do pacote, além de um diretório adicional chamado nome_do_pacote-versão.dist-info. Este diretório contém vários arquivos, incluindo um arquivo METADATA que é equivalente a PKG-INFO em sdists, bem como RECORD. Isso pode ser útil para garantir que nenhum arquivo esteja faltando em suas rodas.

O nome do arquivo de um wheel (ignorando alguns recursos raramente usados) é assim: nome_do_pacote-versão-tag_python-tag_abi-tag_plataforma.whl. Esta convenção de nomenclatura identifica com quais plataformas e versões do Python a roda é compatível. Por exemplo, o nome pip-23.3.1-py3-none-any.whl significa que:

  • (py3) Este wheel pode ser instalado em qualquer implementação do Python 3, seja CPython, a implementação Python mais usada, ou uma implementação alternativa como PyPy;

  • (none) Não depende da versão do Python;

  • (qualquer) Não depende da plataforma.

O padrão py3-none-any é comum para projetos Python puros. Pacotes com módulos de extensão normalmente enviam vários wheels com tags mais complexas.

Todos os detalhes técnicos sobre o formato do wheel podem ser encontrados na especificação de wheels.

E quanto a eggs?#

“Egg” é um formato de pacote antigo que foi substituído pelo formato de wheels. Não deve mais ser usado. Desde agosto de 2023, o PyPI rejeita envios de eggs.

Aqui está uma análise das diferenças importantes entre wheel e egg.

  • O formato egg foi introduzido pelo Setuptools em 2004, enquanto o formato wheel foi introduzido pela PEP 427 em 2012.

  • Wheel tem uma especificação de padrão oficial. Egg não tinha.

  • Wheel é um formato de distribuição, ou seja, um formato de empacotamento. [2] Egg era um formato de distribuição e um formato de instalação em tempo de execução (se deixado compactado), e foi projetado para ser importável.

  • Os arquivos wheel não incluem arquivos .pyc. Portanto, quando a distribuição contém apenas arquivos Python (ou seja, sem extensões compiladas) e é compatível com Python 2 e 3, é possível que um wheel seja “universal”, semelhante a um sdist.

  • Wheel usa diretórios .dist-info padrões. Egg usava .egg-info.

  • Wheel tem uma convenção de nomenclatura de arquivo mais rica. Um único arquivo de wheel pode indicar sua compatibilidade com várias versões e implementações da linguagem Python, ABIs e arquiteturas de sistema.

  • Wheel é versionado. Cada arquivo wheel contém a versão da especificação wheel e a implementação que a empacotou.

  • Wheel é internamente organizado pelo tipo de caminho sysconfig, portanto tornando mais fácil converter para outros formatos.