バイナリ配布物のフォーマット#

このページでは、Python パッケージのバイナリ配布物フォーマット、これは wheel フォーマットとも呼ばれているものですが、これについて仕様を記述します。

wheel は、特別にフォーマットされたファイル名と .whl 拡張子を持った ZIP フォーマットのアーカイブです。 PEP 376 にほぼ従ってインストールされる単一の配布物を含んでいます。特別なインストーラを使うことが推奨されていますが、 wheel ファイルは、いつでも後でその最終的なパス名の場所に内容物を展開するに足りる情報を保存しつつ、サイトパッケージを置くべき場所に標準の 'unxip' ツールで単純にアンパックすればインストールできます。

詳細#

wheel の 'distribution-1.0-py32-none-any.whl' をインストールする#

Wheel によるインストールは、概念上、ふたつの段階から構成されています:

  • アンパックする。

    1. distribution-1.0.dist-info/WHEEL をパースします。

    2. インストーラが Wheel のバージョンと互換であることを確認します。マイナーバージョンが大きければ警告し、メジャーバージョンが大きければ処理を中断します。

    3. もし、 Root-Is-Purelib == 'true' であれば、アーカイブを purelib (site-packages) へアンパックします。

    4. そうでなければ、アーカイブを platlib (site-packages) へアンパックします。

  • 広げる。

    1. アンパックされたアーカイブは、 distribution-1.0.dist-info/ と (データ部分があれば) distribution-1.0.data/ を含んでいます。

    2. distribution-1.0.data/ の下の全てのサブツリーを、その目的地となるディレクトリパスに移動します。 distribution-1.0.data/(purelib|platlib|headers|scripts|data) のような distribution-1.0.data/ の下のそれぞれのサブディレクトリは、目的地となるディレクトリパスの辞書になっています。初期状態でサポートされているパスは distutils.command.install から取り込まれます。

    3. もし該当するならば、 #!python から始まるスクリプト群が適切なインタープリタを指し示すように更新します。

    4. distribution-1.0.dist-info/RECORD をインストール先のディレクトリパスに更新します。

    5. 空の distribution-1.0.data ディレクトリを削除します。

    6. インストールされた .py のファイルを全て .pyc にコンパイルします。 (アンインストーラは、 RECORD で言及されていなくても .pyc ファイルを削除できるほどに賢くあるべきです。)

ファイルフォーマット#

ファイル名の慣習#

wheel のファイル名は(訳注、慣習として) {配布物}-{バージョン}(-{ビルドタグ})?-{python タグ}-{abi タグ}-{プラットフォームタグ}.whl <{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl> です。

配布物

配布物の名前、例えば 'django' や 'pyramid' 。

version

配布物のバージョン、例えば 1.0 。

ビルドタグ

省略可能なビルドナンバー。数字で始まらなければなりません。ふたつの wheel ファイルのファイル名がこの部分を除いて (すなわち名称やバージョンやその他のタグが) 同一であった場合のタイブレーカ (訳注、いずれかに決めるための方法) として働きます。指定されていれば先頭の数字を int として解釈して第1要素とし残りを str として解釈して第2要素とするような要素数が2個のタプルとして解釈され、指定されていない場合には空欄のタプルとして解釈されます。

ビルド番号に係るよくあるユースケースは、CPython のリリース前のバージョンを使って manylinux で配布物をビルドしている場合のように、ビルド環境に変化があったためにバイナリ配布物を再ビルドする時です。

警告

ビルド番号は、配布物のバージョンの一部ではなく、従って外部からの、特に Python エコシステムのツール類や標準類の外側からの参照は困難です。配布物が外部からの参照を必要とするようなケースとしては、セキュリティ上の脆弱性を解決しようとする場合がよくあるケースです。

この制約のゆえに、外部から参照される必要がある新たな配布物は、新しい配布物をビルドする際にビルド番号を使う べきではありません 。そのような場合には、代わりに 新しい配布物バージョン が作成されるべきです。

言語の実装とバージョンタグ

例えば、 'py27' ・ 'py2' ・ 'py3' 。

abi タグ

例えば、 'cp33m' ・ 'abi3' ・ 'none' 。

プラットフォームタグ

例えば、 'linux_x86_64' ・ 'any' 。

例えば、 distribution-1.0.1-py27-none-any.whl とは、 'distribution' と呼ばれるパッケージの最初のビルドであって、 Python 2.7 (任意の Python 2.7 実装) で動作し、 ABI を持たず (即ち純 Python) 、任意の CPU アーキテクチャで動作するということです。

ファイル名の拡張子を除いて最後の3個の構成要素は、"互換性タグ" と呼ばれます。互換性タグは、インタープリタに対するそのパッケージの基本的な要求事項を表現しており、 PEP 425 に詳しく書かれています。

エスケープとユニコード#

ファイル名の構成要素がダッシュ (- つまり HYPHEN-MINUS) で分離されているので、この文字は構成要素の中に出現してはなりません。これは次のように取り扱われます:

  • 配布物の名前の部分では、 -_. (HYPHEN-MINUS ・ LOW LINE ・ FULL STOP) の文字は、すべて _ (LOW LINE) で置き換えられるべきで、かつ、大文字はすべて対応する小文字に置き換えられるべきです。これは、通常の 名前の正規化 の後に -_ で置き換えるのと同じです。 wheel ファイルを入力とするツールは、 . (FULL STOP) や大文字を受け入れる準備ができていなければなりませんが、しかし、それはこの仕様の初期のバージョンで許されていたからです。

  • バージョン番号は、 バージョン指定子仕様 に従って正規化されなければなりません。正規化済みのバージョン番号は - を含んでいてはなりません。

  • 残りの構成要素は、 - 文字を含んでいてはいけないので、エスケープ処理を行う必要がありません。

wheel ファイルを生成するツールはファイル名部分の構成要素が - を含んでいないことを検証しなければなりませんが、これは、もし含んでいれば結果として生成されたファイルが正しく処理されないかもしれないからです。

アーカイブのファイル名は Unicode です。いくつかのツールでは非 ASCII 文字のファイル名をサポートするように更新されるまでに幾らかの時間がかかるかもしれませんが、しかし、この仕様ではサポートされているのです。

アーカイブの 中にある ファイルのファイル名は、 UTF-8 でエンコードされています。 ZIP クライアントのいくつかは共通して UTF-8 のファイル名を正常に表示しませんが、このエンコーディングは ZIP の仕様でも Python の zipfile の仕様でも、共にサポートされています。

ファイルの内容#

{distribution} の部分を例えば beaglevote のようなパッケージ名で置き換え、 {version} の部分を例えば 1.0.0` のようなバージョン番号で置き換えた wheel ファイルの内容は次のもので構成されています:

  1. アーカイブのルートディレクトリに当たる / は、WHEEL で指定されている通り purelib または platlib にインストールされるファイルをすべて含んでいます。通常は、 purelibplatlib はいずれも site-packages です。

  2. {distribution}-{version}.dist-info/ はメタデータを含んでいます。

  3. {distribution}-{version}.data/ は、すでにカバーされているものは別として、サブディレクトリ名がインストールパスの辞書への (例えば、 data, scripts, headers, purelib, platlib のような) 指示子になっているような、空ではないそれぞれのインストールスキームに対応したディレクトリを含みます。

  4. Python スクリプトは scripts ディレクトリに置かなければならず、また、インストール時のスクリプトラッパの生成や #!python 書き換えといった利点を活用するために、正確に b'#!python' で始まっていなければなりません。

  5. {distribution}-{version}.dist-info/METADATA は、バージョン 1.1 またはそれ以上のフォーマットのメタデータです。

  6. {distribution}-{version}.dist-info/WHEEL は、同様のキー:バリュー形式で表現されたアーカイブそのものに関するメタデータです:

    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 は、Wheel の仕様のバージョン番号です。

  8. Generator は、そのアーカイブを作成したソフトウェアの名前で、バージョン番号を付加しても構いません。

  9. Root-Is-Purelib は、アーカイブの最上位のディレクトリが pure lib へインストールされるべきものであれば true で、そうでなければ platlib へインストールされます。

  10. Tag は、 wheel の拡張互換性タグで、例の中ではファイル名の py2.py3-none-any の部分です。

  11. Build は、ビルドナンバーで、もしビルドナンバーがなければ省略されます。

  12. wheel インストーラは、もし自身がサポートしているものより Wheel-Version が大きければ警告するべきですし、 Wheel-Version のメジャーバージョンがサポートしているものより大きい場合にはフェイルするべきです。

  13. Wheel は複数のバージョンの Python を跨いでも動作するように意図されたインストール用のフォーマットですが、通常は .pyc ファイルを含みません。

  14. Wheel は、 setup.py ないし setup.cfg を含みません。

このバージョンの wheel 仕様は、distutils のインストール方法論に基づいていて、ファイルを他の場所にインストールする方法については定義していません。既存の wininst や egg バイナリフォーマットが提供する機能の上位互換のレイアウトを提案します。

.dist-info ディレクトリ#
  1. Wheel の .dist-info ディレクトリは、最低限でも METADATA ・ WHEEL ・RECORD を含みます。

  2. METADATA はパッケージのメタデータで、 sdists のルートディレクトリにある PKG-INFO と同じフォーマットで記述されます。

  3. WHEEL は、パッケージをビルドする部分に特化した wheel のメタデータです。

  4. RECORD は、 wheel に含まれる (ほとんど) 全てのファイルとそのセキュアなハッシュ値のリストです。 PEP 376 とは異なり、自分自身のハッシュ値を内包することは不可能な RECORD を除く各ファイルのハッシュ値が含まれていなければなりません。署名済みの wheel ファイルがアーカイブの完全性を検証するのに RECORD 内の暗号学的に強いハッシュ値に依存しているので、ハッシュ計算のアルゴリズムは sha256 以上でなければならず、特に md5 とsha1 は許されません。

  5. PEP 376 の INSTALLER と REQUESTED はアーカイブに含まれません。

  6. RECORD.jws は、デジタル署名のために使われます。これについては RECORD では触れられません。

  7. 自分の wheel ファイルを S/MIME 署名でセキュアにすることを好む人は、 RECORD.p7s を使うことができます。これについては RECORD では触れられません。

  8. 展開中に wheel インストーラは RECORD 内のハッシュ値と実際のファイルの内容 (のハッシュ値) をすべて検証します。 RECORD とその署名による検証に加えて、アーカイブの中のいずれかのファイルが RECORD にリストされていないか、または、正しくハッシュされていない時にはインストールがフェイルするでしょう。

.data ディレクトリ#

site-packages 内に通常通りにインストールされなかったファイルはどれでも、.dist-info ディレクトリと同様に命名されるが .data/ 拡張子をつけられて .data ディレクトリに行きます:

distribution-1.0.dist-info/

distribution-1.0.data/

.data ディレクトリでは、配布物からのスクリプト・ヘッダー・説明文書などをサブディレクトリに収めています。インストール中に、これらのサブディレクトリの内容を行き先となるパスへ動かします。

署名済み wheel ファイル#

Wheel ファイルは、デジタル署名を可能にした拡張 RECORD ファイルを含みます。 PEP 376 の RECORD は、 md5sum の代わりにセキュアなハッシュ値 digestname=urlsafe_b64encode_nopad(digest) (末尾に = 文字を追加しない url セーフな base64 エンコード) を二つ目のカラムとするように修正されました。生成された .pyc ファイルなども含むすべての可能なエントリにハッシュ値が付加されていますが、 RECORD は自身のハッシュ値を含むことができないので例外です。例えば:

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

署名用のファイルである RECORD.jws と RECORD.p7s は、 RECORD ファイルが作成された後にしか追加できないので、RECORD ファイル内で言及されることは全くありません。アーカイブの中の他のファイルはすべて、RECORD ファイル内に正しいハッシュ値を持たなければならず、そうでなければインストールに失敗します。

JSON ウェブ署名が使われる場合には、ひとつかそれ以上の JSON Web Signature JSON Serialization (JWS-JS) 署名が RECORD ファイルの隣にある RECORD.jws ファイルの中に保存されます。 RECORD ファイルの SHA-256 ハッシュ値を署名の JSON ペイロードに含むことで RECORD ファイルに署名するために JWS が使われます:

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

(ハッシュ値の書き方のフォーマットは RECORD で使われるものと同じです。)

RECORD.p7s を使う場合は、このファイルに RECORD ファイルに関する分離型の S/MIME 署名を入れておかなければなりません。

wheel インストーラはデジタル署名を理解することを要求されてはいませんが、 RECORD ファイル内のハッシュ値が展開されたファイル内容に対して妥当であることを検証しなければなりません。インストーラが RECORD ファイルに対するハッシュ値を確認する際には、別途用意された署名検証プログラムは RECORD ファイルが署名に対して妥当であることだけを確認すれば十分です。

以下を参照のこと

FAQ#

Wheel は .data ディレクトリを定義します。すべてのデータをそこに入れるべきでしょうか?#

この仕様では、あなたがあなたのソースコードをどのように組織立てて置くべきかについては特に意見を表明していません。 .data ディレクトリは、 site-packages 内や PYTHONPATH 内に通常ならインストールされない全てのファイルを置く場所というだけのことです。換言すれば、 そのような ファイル群が通常なら wheel の .data ディレクトリに置く形で配布されない時でさえも、 pkgutil.get_data(package, resource) を使い続けても構わないのです。

なぜ wheel は添付された署名を持つのか?#

添付された署名は、アーカイブと一体のものとして転送されるので、分離署名よりも便利です。個々のファイルが署名されているだけなので、アーカイブを圧縮し直しても署名が無効にならず、また、アーカイブ全体をダウンロードしなくても個々のファイルの検証を行うことができます。

なぜ wheel は JWS 署名を許容するのか?#

JWS がその一部を構成する JOSE の仕様は実装を容易にするように設計されており、その性質は wheel の基本的な設計目標のひとつでもあります。 JWS は使いやすくて簡潔な純 Python の実装をもたらします。

なぜ wheel は S/MIME 署名をも許容するのか?#

S/MIME 署名は、既存の公開鍵基盤を wheel でも採用する必要があるか、または、採用したいユーザのために許容されています。

署名されたパッケージは、セキュアなパッケージ更新システムを構成するひとつのビルディングブロックであるというだけのものです。 Wheel としては、単にビルディングブロックを提供するだけです。

"pure lib" と "plat lib" って、どう扱えばいいの?#

Wheel は "purelib" と "platlib" を区別して扱いますが、これらはプラットフォームによっては大きな違いがあります。例えば、 Fedora では純 Python のパッケージを '/usr/lib/pythonX.Y/site-packages' にインストールし、プラットフォームに依存しないパッケージを '/usr/lib64/pythonX.Y/site-packages' にインストールします。

{name}-{version}.data/purelib 内の全てのファイルについて "Root-Is-Purelib: false" という設定になっている wheel ファイルは、同じファイルがルートディレクトリ内にあって "Root-Is-Purelib: true" になっている wheel ファイルと相同であり、 "purelib" と "platlib" の両カテゴリにファイル群が存在することには問題がありません。

実際のところ、 wheel は、それが純 Python なのかそうでないのかによって "purelib" または "platlib" のいずれか一方しか持たず、 "Root-Is-purelib" を適切に設定しつつファイル群をルートディレクトリに置くべきです。

Python のソースコードを wheel ファイルから直接にインポートすることはできますか?#

技術的な話としては、単純に展開するだけでインストールできる機能と zipimport と互換性のあるアーカイブフォーマットの両方をサポートしているので、一部の wheel ファイルは sys.path に直接に置くことを サポートしています 。しかし、このような動作はフォーマット設計の自然な結果とはいうものの、実際にはこれに依存することは一般的には推奨されていません。

第一に、wheel は一義的に配布物のフォーマットとして 設計されている ので、インストールの段階を省略するということは、完全なインストールがなされているものと仮定している機能 (例えば、正当性監査やセキュリティアップデートのために正しく追跡することができるひとつの方法であるところの pipvirtualenv のような標準ツールを使った依存関係の捕捉や管理の機能、あるいは、ヘッダファイルを適切な場所に公開することによって C 言語拡張をビルドする標準的な機構を完全に統合する機能) への信頼を故意に避けることになります。

第二に、 Python のソフトウェアの中には zip アーカイブから直接に動作させることをサポートするように書かれているものもありますが、やはり完全にインストールされることを前提にして書かれたソースコードが今も普通です。 zip アーカイブからソフトウェアを走らせようと試みてこの仮定を崩すと、 (とりわけ失敗動作がサードパーティのライブラリで起きる場合には) しばしば失敗動作がわかりにくく原因究明が困難になるでしょう。この問題の最もありがちなふたつの根源は、 C 言語拡張を zip アーカイブからインポートすることが CPython では サポートされていない こと (というのは、どんなプラットフォームの動的ローディング機構でもこのような動作が直接にはサポートされていないから) 、および、 zip アーカイブから動作する際には __file__ 属性がもはや普通のファイルシステム上のファイルパスを参照しておらず、 zip アーカイブのファイルシステム上の置き場所のパスとアーカイブ内のモジュールへの相対パスの両方を含んだ連結パスになることです。ソフトウェア内では抽象化された資源への API 群を正しく扱えたとしても、外部の部品とのインタフェイスは依然として実際にディスク上に存在するファイルがないと動作できないかもしれません。

メタクラスと同様に、モンキーパッチングとメタパスからのインポートは、この機能を使う利点をあなたが本当に必要としていると確信しているのでなければ、おそらくあなたはこの機能を使う必要がないと思われます。兎にも角にもこれを使う 意思を固めた のであれば、 (訳注、この機能を使った時に生じた) ある動作不良を真性のバグであると認めてもらう前に、多くのプロジェクトではそれを完全インストールの状態で再現するように要求されるであろうということを認識しておいてください。

歴史#

  • 2013年2月: PEP 427 を通じてこの仕様書が承認されました。

  • 2021年2月: 他の普及しているツールが実際にやるのと同じやり方に合わせて、 wheel のファイル名におけるエスケーピングの規則が修正されました。

補遺#

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)