Formato de distribuição binária#

This page specifies the binary distribution format for Python packages, also called the wheel format.

Um wheel é um arquivo em formato ZIP com um nome de arquivo formatado de forma especial e a extensão .whl. Ele contém uma única distribuição quase como seria instalado de acordo com a PEP 376 com um esquema de instalação particular. Embora um instalador especializado seja recomendado, um arquivo wheel pode ser instalado simplesmente descompactando em pacotes de sites com a ferramenta padrão de “descompactação” enquanto preserva informações suficientes para espalhar seu conteúdo em seus caminhos finais a qualquer momento.

Detalhes#

Instalando um wheel ‘distribution-1.0-py32-none-any.whl’#

A instalação do wheel consiste, em teoria, em duas fases:

  • Desempacotar.

    1. Analisar distribution-1.0.dist-info/WHEEL.

    2. Verificar se o instalador é compatível com a versão Wheel. Avisa se a versão secundária é maior, cancela se a versão principal é maior.

    3. Se Root-Is-Purelib == ‘true’, descompactar o arquivo em purelib (sites-packages).

    4. Caso contrário, descompactar o arquivo em platlib (site-packages).

  • Espalhar.

    1. O arquivo descompactado inclui distribution-1.0.dist-info/ e (se houver dados) distribution-1.0.data/.

    2. Move each subtree of distribution-1.0.data/ onto its destination path. Each subdirectory of distribution-1.0.data/ is a key into a dict of destination directories, such as distribution-1.0.data/(purelib|platlib|headers|scripts|data). These subdirectories are installation paths defined by sysconfig.

    3. Se aplicável, atualizar os scripts começando com #!python para apontar para o interpretador correto.

    4. Atualizar distribution-1.0.dist-info/RECORD com os caminhos instalados.

    5. Remover o diretório vazio distribution-1.0.data.

    6. Compilar qualquer .py instalado em .pyc. (Os desinstaladores devem ser inteligentes o suficiente para remover .pyc, mesmo que não seja mencionado em RECORD.)

Formato de arquivos#

Convenção de nome de arquivos#

O nome de arquivo do wheel é {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl.

distribution

Nome da distribuição (p.ex., ‘django’, ‘pyramid’).

version

Versão da distribuição (p.ex., 1.0).

build tag

Número de construção opcional. Deve começar com um dígito. Atua como um desempate se os nomes de arquivo de dois wheels forem iguais em todos os outros aspectos (ou seja, nome, versão e outras tags). Classifique como uma tupla vazia se não for especificado, caso contrário, classifique como uma tupla de dois itens com o primeiro item sendo os dígitos iniciais como um int, e o segundo item sendo o restante da tag como um str.

Um caso de uso comum para números de construção é reconstruir uma distribuição binária devido a uma mudança no ambiente de construção, como ao usar a imagem manylinux para construir distribuições usando versões de pré-lançamento do CPython.

Aviso

Os números de construção não fazem parte da versão de distribuição e, portanto, são difíceis de se referir externamente, especialmente fora do ecossistema do Python de ferramentas e padrões. Um caso comum em que uma distribuição precisa ser referenciada externamente é quando se resolve uma vulnerabilidade de segurança.

Devido a esta limitação, novas distribuições que precisam ser referenciadas externamente não devem usar números de construção ao construir a nova distribuição. Em vez disso, deve ser criada uma nova versão de distribuição para esses casos.

implementação da linguagem e tag de versão

p.ex., ‘py27’, ‘py2’, ‘py3’.

abi tag

p.ex., ‘cp33m’, ‘abi3’, ‘none’.

platform tag

p.ex., ‘linux_x86_64’, ‘any’.

Por exemplo, distribution-1.0-1-py27-none-any.whl é a primeira construção de um pacote chamado ‘distribution’ e é compatível com Python 2.7 (qualquer implementação Python 2.7), com sem ABI (puro Python), em qualquer arquitetura de CPU.

Os últimos três componentes do nome do arquivo antes da extensão são chamados de “tags de compatibilidade”. As tags de compatibilidade expressam os requisitos básicos do interpretador do pacote e são detalhadas na PEP 425.

Escape e Unicode#

Como os componentes do nome do arquivo são separados por um traço (-, HYPHEN-MINUS), este caractere não pode aparecer em nenhum componente. Isso é tratado da seguinte maneira:

  • Em nomes de distribuição, qualquer ocorrência de caracteres -_. (HYPHEN-MINUS, LOW LINE e FULL STOP) deve ser substituída por _ (LOW LINE) e caracteres maiúsculos devem ser substituídos pelos minúsculos correspondentes. Isso é equivalente à normalização de nome regular da PEP 503 seguida pela substituição de - por _. Porém, ferramentas que usem wheels devem estar preparadas para aceitar . (FULL STOP) e letras maiúsculas, pois estes eram permitidos por uma versão anterior desta especificação.

  • Os números de versão devem ser normalizados de acordo com a especificação de especificadores de versão. Os números de versão normalizados não podem conter -.

  • Os componentes restantes não podem conter caracteres -, portanto, nenhum escape é necessário.

Ferramentas que produzem wheels devem verificar se os componentes do nome de arquivo não contêm -, pois o arquivo resultante pode não ser processado corretamente se contiverem.

O nome do arquivo é Unicode. Levará algum tempo até que as ferramentas sejam atualizadas para oferecer suporte a nomes de arquivos não ASCII, mas eles são suportados nesta especificação.

Os nomes de arquivos dentro do arquivo são codificados como UTF-8. Embora alguns clientes ZIP em uso comum não exibam nomes de arquivo UTF-8 apropriadamente, a codificação é suportada pela especificação ZIP e pelo zipfile do Python.

Conteúdo dos arquivos#

O conteúdo de um arquivo wheel, onde {distribution} é substituído pelo nome do pacote, por exemplo, beaglevote e {version} é substituído por sua versão, p.ex., 1.0.0, consiste em:

  1. /, a raiz do arquivo, contém todos os arquivos a serem instalados em purelib ou platlib conforme especificado em WHEEL. purelib e platlib são normalmente site-packages.

  2. {distribution}-{version}.dist-info/ contém metadados.

  3. {distribution}-{version}.data/ contém um subdiretório para cada chave de esquema de instalação não vazia ainda não coberta, onde o nome do subdiretório é um índice em um dicionário de caminhos de instalação (p.ex., data, scripts, headers, purelib, platlib).

  4. Scripts Python devem aparecer em scripts e começar exatamente com b'#!python' para desfrutar da geração do wrapper de script e reescrever #!python no momento da instalação. Eles podem ter qualquer ou nenhuma extensão.

  5. {distribution}-{version}.dist-info/METADATA é Metadata versão 1.1 ou metadados de formato superior.

  6. {distribution}-{version}.dist-info/WHEEL são metadados sobre p arquivo em si no mesmo formato “chave: valor”:

    Wheel-Version: 1.0
    Generator: bdist_wheel 1.0
    Root-Is-Purelib: true
    Tag: py2-none-any
    Tag: py3-none-any
    Build: 1
    
  7. Wheel-Version é o número de versão da especificação Wheel.

  8. Generator é o nome e, opcionalmente, a versão do software que produziu o arquivo.

  9. Root-Is-Purelib é verdadeiro se o diretório de nível superior do arquivo deve ser instalado em purelib; caso contrário, a raiz deve ser instalada no platlib.

  10. Tag são as tags de compatibilidade expandida do wheel; no exemplo, o nome do arquivo conteria py2.py3-none-any.

  11. Build é o número da construção e é omitido se não houver número da construção.

  12. Um instalador de wheel deve avisar se Wheel-Version é maior do que a versão que ele suporta, e deve falhar se Wheel-Version tiver uma versão principal maior do que a versão que ele suporta.

  13. O Wheel, sendo um formato de instalação destinado a funcionar em várias versões do Python, geralmente não inclui arquivos .pyc.

  14. Wheel não contém setup.py ou setup.cfg.

Esta versão da especificação do wheel é baseada nos esquemas de instalação do distutils e não define como instalar arquivos em outros locais. O layout oferece um superconjunto da funcionalidade fornecida pelos formatos binários wininst e egg existentes.

O diretório .dist-info#
  1. Os diretórios .dist-info de wheels incluem no mínimo METADATA, WHEEL e RECORD.

  2. METADATA são os metadados do pacote, o mesmo formato do PKG-INFO encontrado na raiz dos sdists.

  3. WHEEL são os metadados de wheel específicos para uma construção do pacote.

  4. RECORD é uma lista de (quase) todos os arquivos no wheel seus hashes seguros. Ao contrário da PEP 376, nenhum arquivo, exceto RECORD, pode conter um hash de si mesmo, deve incluir seu hash. O algoritmo hash deve ser sha256 ou melhor; especificamente, md5 e sha1 não são permitidos, pois os arquivos wheel assinados contam com hashes fortes em RECORD para validar a integridade do arquivo.

  5. INSTALLER e REQUESTED da PEP 376 não são incluídos no arquivo.

  6. RECORD.jws é usado para assinaturas digitais. Não é mencionado no RECORD.

  7. RECORD.p7s é permitido como cortesia para qualquer pessoa que prefira usar assinaturas S/MIME para proteger seus arquivos wheels. Não é mencionado no RECORD.

  8. Durante a extração, os instaladores de wheels verificam todos os hashes em RECORD em relação ao conteúdo do arquivo. Além de RECORD e suas assinaturas, a instalação falhará se qualquer arquivo no arquivo não for mencionado e com hash correto em RECORD.

O diretório .data#

Qualquer arquivo que não é normalmente instalado dentro de site-packages vai para o diretório .data, nomeado como o diretório .dist-info, mas com a extensão .data/:

distribution-1.0.dist-info/

distribution-1.0.data/

O diretório .data contém subdiretórios com os scripts, cabeçalhos, documentação e assim por diante da distribuição. Durante a instalação, o conteúdo desses subdiretórios é movido para seus caminhos de destino.

Arquivos wheels assinados#

Os arquivos wheels incluem um RECORD estendido que permite assinaturas digitais. O RECORD da PEP 376 foi alterado para incluir um hash seguro digestname=urlsafe_b64encode_nopad(digest) (codificação base64 de urlsafe sem caracteres ao final =) como a segunda coluna em vez de um md5sum. Todas as entradas possíveis são hash, incluindo quaisquer arquivos gerados, como arquivos .pyc, mas não RECORD, que não pode conter seu próprio hash. Por exemplo:

file.py,sha256=AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT\_pNh2yI,3144
distribution-1.0.dist-info/RECORD,,

O(s) arquivo(s) de assinatura RECORD.jws e RECORD.p7s não são mencionados em RECORD, pois eles só podem ser adicionados após RECORD ser gerado. Todos os outros arquivos no arquivo devem ter um hash correto em RECORD ou a instalação falhará.

If JSON web signatures are used, one or more JSON Web Signature JSON Serialization (JWS-JS) signatures is stored in a file RECORD.jws adjacent to RECORD. JWS is used to sign RECORD by including the SHA-256 hash of RECORD as the signature’s JSON payload:

{ "hash": "sha256=ADD-r2urObZHcxBW3Cr-vDCu5RJwT4CaRTHiFmbcIYY" }

(O valor hash é o mesmo formato usado em RECORD.)

Se RECORD.p7s for usado, ele deve conter uma assinatura de formato S/MIME separada de RECORD.

Um instalador de wheel não precisa entender as assinaturas digitais, mas DEVE verificar os hashes em RECORD em relação ao conteúdo do arquivo extraído. Quando o instalador verifica os hashes do arquivo em relação ao RECORD, um verificador de assinatura separado só precisa estabelecer se RECORD corresponde à assinatura.

Veja

FAQ#

Wheel define um diretório .data. Devo colocar todos os meus dados lá?#

Esta especificação não tem uma opinião sobre como você deve organizar seu código. O diretório .data é apenas um lugar para quaisquer arquivos que não são normalmente instalados dentro de site-packages ou no PYTHONPATH. Em outras palavras, você pode continuar a usar pkgutil.get_data(package, resource) ainda que esses arquivos normalmente não sejam distribuídos no diretório .data do wheel.

Por que o wheel inclui assinaturas anexadas?#

Assinaturas anexadas são mais convenientes do que assinaturas separadas porque elas viajam com o arquivo. Uma vez que apenas os arquivos individuais são assinados, o arquivo pode ser recompactado sem invalidar a assinatura ou os arquivos individuais podem ser verificados sem ter que baixar todo o arquivo.

Por que wheel permite assinaturas JWS?#

As especificações JOSE das quais o JWS faz parte foram projetadas para serem fáceis de implementar, um recurso que também é um dos principais objetivos do projeto da roda. O JWS produz uma implementação puro Python concisa e útil.

Por que wheel também permite assinaturas S/MIME?#

Assinaturas S/MIME são permitidas para usuários que precisam ou querem usar uma infraestrutura de chaves públicas existente com o wheel.

Pacotes assinados são apenas um bloco de construção básico em um sistema de atualização segura de pacotes. Wheel só fornece o bloco de construção.

Qual é a diferença entre “purelib” e “platlib”?#

O Wheel preserva a distinção “purelib” vs. “platlib”, que é significativa em algumas plataformas. Por exemplo, o Fedora instala pacotes puro Python em ‘/usr/lib/pythonX.Y/site-packages’ e pacotes dependentes de plataforma em ‘/usr/lib64/pythonX.Y/site-packages’.

Um wheel com “Root-Is-Purelib: false” com todos os seus arquivos em {nome}-{versão}.data/purelib é equivalente a um wheel com “Root-Is-Purelib: true” com os mesmos arquivos na raiz, e é válido ter arquivos nas categorias “purelib” e “platlib”.

Na prática, um wheel deve ter apenas um de “purelib” ou “platlib” dependendo se é puro Python ou não e esses arquivos devem estar na raiz com a configuração apropriada fornecida para “Root-is-purelib”.

É possível importar código Python diretamente de um arquivo wheel?#

Tecnicamente, devido à combinação de suporte de instalação via extração simples e usando um formato de arquivo compatível com zipimport, um subconjunto de arquivos wheel oferece suporte a ser colocado diretamente no sys.path. No entanto, embora esse comportamento seja uma consequência natural do design do formato, não é recomendável confiar nele.

Em primeiro lugar, o wheel é projetado principalmente como um formato de distribuição, então pular a etapa de instalação também significa evitar deliberadamente qualquer dependência de recursos que pressupõem a instalação completa (como ser capaz de usar ferramentas padrão como pip e virtualenv para capturar e gerenciar dependências de uma forma que possam ser devidamente rastreadas para fins de auditoria e atualização de segurança, ou integração completa com o maquinário de construção padrão para extensões C, publicando arquivos de cabeçalho no local apropriado).

Em segundo lugar, embora alguns softwares Python sejam escritos para suportar a execução direta de um arquivo zip, ainda é comum que o código seja escrito assumindo que foi totalmente instalado. Quando essa suposição é quebrada ao tentar executar o software a partir de um arquivo zip, as falhas podem frequentemente ser obscuras e difíceis de diagnosticar (especialmente quando ocorrem em bibliotecas de terceiros). As duas fontes mais comuns de problemas com isso são o fato de que a importação de extensões C de um arquivo zip não ser suportada pelo CPython (uma vez que fazer isso não é suportado diretamente pela máquina de carregamento dinâmico em qualquer plataforma) e que quando executando a partir de um arquivo zip, o atributo __file__ não se refere mais a um caminho de sistema de arquivos comum, mas a um caminho de combinação que inclui tanto a localização do arquivo zip no sistema de arquivos quanto o caminho relativo para o módulo dentro do arquivo. Mesmo quando o software usa corretamente as APIs de recursos abstratos internamente, a interface com componentes externos ainda pode exigir a disponibilidade de um arquivo real no disco.

Como metaclasses, monkeypatching e importadores de metacaminho, se você ainda não tem certeza de que precisa tirar proveito desse recurso, é quase certo que não precisa dele. Se você decidir usá-lo de qualquer maneira, esteja ciente de que muitos projetos exigirão que uma falha seja reproduzida com um pacote totalmente instalado antes de aceitá-lo como um bug genuíno.

Histórico#

  • February 2013: This specification was approved through PEP 427.

  • February 2021: The rules on escaping in wheel filenames were revised, to bring them into line with what popular tools actually do.

Apêndice#

Exemplo de implementação de urlsafe-base64-nopad:

# urlsafe-base64-nopad for Python 3
import base64

def urlsafe_b64encode_nopad(data):
    return base64.urlsafe_b64encode(data).rstrip(b'=')

def urlsafe_b64decode_nopad(data):
    pad = b'=' * (4 - (len(data) & 3))
    return base64.urlsafe_b64decode(data + pad)