peps/pep-0491/index.html

681 lines
52 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>PEP 491 The Wheel Binary Package Format 1.9 | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0491/">
<link rel="stylesheet" href="../_static/style.css" type="text/css">
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
<meta property="og:title" content='PEP 491 The Wheel Binary Package Format 1.9 | peps.python.org'>
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0491/">
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
<meta property="og:image:alt" content="Python PEPs">
<meta property="og:image:width" content="200">
<meta property="og:image:height" content="200">
<meta name="description" content="Python Enhancement Proposals (PEPs)">
<meta name="theme-color" content="#3776ab">
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
<title>Following system colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"></circle>
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
<title>Selected dark colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
<title>Selected light colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
</svg>
<script>
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
</script>
<section id="pep-page-section">
<header>
<h1>Python Enhancement Proposals</h1>
<ul class="breadcrumbs">
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li>
<li><a href="../pep-0000/">PEP Index</a> &raquo; </li>
<li>PEP 491</li>
</ul>
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
</button>
</header>
<article>
<section id="pep-content">
<h1 class="page-title">PEP 491 The Wheel Binary Package Format 1.9</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Daniel Holth &lt;dholth&#32;&#97;t&#32;gmail.com&gt;</dd>
<dt class="field-even">Discussions-To<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://mail.python.org/archives/list/distutils-sig&#64;python.org/">Distutils-SIG list</a></dd>
<dt class="field-odd">Status<span class="colon">:</span></dt>
<dd class="field-odd"><abbr title="Inactive draft that may be taken up again at a later time">Deferred</abbr></dd>
<dt class="field-even">Type<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
<dt class="field-odd">Topic<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="../topic/packaging/">Packaging</a></dd>
<dt class="field-even">Created<span class="colon">:</span></dt>
<dd class="field-even">16-Apr-2015</dd>
</dl>
<hr class="docutils" />
<section id="contents">
<details><summary>Table of Contents</summary><ul class="simple">
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#pep-deferral">PEP Deferral</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#details">Details</a><ul>
<li><a class="reference internal" href="#installing-a-wheel-distribution-1-0-py32-none-any-whl">Installing a wheel distribution-1.0-py32-none-any.whl</a><ul>
<li><a class="reference internal" href="#recommended-installer-features">Recommended installer features</a></li>
<li><a class="reference internal" href="#recommended-archiver-features">Recommended archiver features</a></li>
</ul>
</li>
<li><a class="reference internal" href="#file-format">File Format</a><ul>
<li><a class="reference internal" href="#file-name-convention">File name convention</a></li>
<li><a class="reference internal" href="#escaping-and-unicode">Escaping and Unicode</a></li>
<li><a class="reference internal" href="#file-contents">File contents</a><ul>
<li><a class="reference internal" href="#the-dist-info-directory">The .dist-info directory</a></li>
<li><a class="reference internal" href="#the-data-directory">The .data directory</a></li>
<li><a class="reference internal" href="#install-paths">Install paths</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#signed-wheel-files">Signed wheel files</a></li>
<li><a class="reference internal" href="#comparison-to-egg">Comparison to .egg</a></li>
</ul>
</li>
<li><a class="reference internal" href="#faq">FAQ</a><ul>
<li><a class="reference internal" href="#wheel-defines-a-data-directory-should-i-put-all-my-data-there">Wheel defines a .data directory. Should I put all my data there?</a></li>
<li><a class="reference internal" href="#why-does-wheel-include-attached-signatures">Why does wheel include attached signatures?</a></li>
<li><a class="reference internal" href="#why-does-wheel-allow-jws-signatures">Why does wheel allow JWS signatures?</a></li>
<li><a class="reference internal" href="#why-does-wheel-also-allow-s-mime-signatures">Why does wheel also allow S/MIME signatures?</a></li>
<li><a class="reference internal" href="#what-s-the-deal-with-purelib-vs-platlib">Whats the deal with “purelib” vs. “platlib”?</a></li>
<li><a class="reference internal" href="#is-it-possible-to-import-python-code-directly-from-a-wheel-file">Is it possible to import Python code directly from a wheel file?</a></li>
</ul>
</li>
<li><a class="reference internal" href="#appendix">Appendix</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p>This PEP describes the second version of a built-package format for Python
called “wheel”. Wheel provides a Python-specific, relocatable package format
that allows people to install software more quickly and predictably than
re-building from source each time.</p>
<p>A wheel is a ZIP-format archive with a specially formatted file name and
the <code class="docutils literal notranslate"><span class="pre">.whl</span></code> extension. It contains a single distribution nearly as it
would be installed according to <a class="pep reference internal" href="../pep-0376/" title="PEP 376 Database of Installed Python Distributions">PEP 376</a> with a particular installation
scheme. Simple wheels can be unpacked onto <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> and used directly
but wheels are usually installed with a specialized installer.</p>
<p>This version of the wheel specification adds support for installing
distributions into many different directories, and adds a way to find
those files after they have been installed.</p>
</section>
<section id="pep-deferral">
<h2><a class="toc-backref" href="#pep-deferral" role="doc-backlink">PEP Deferral</a></h2>
<p>This PEP is not currently being actively pursued, with Python packaging
improvements currently focusing on the package build process rather than
expanding the binary archive format to cover additional use cases.</p>
<p>Some specific elements to be addressed when work on this PEP is resumed in the
future:</p>
<ul class="simple">
<li>migrating the official wheel format definition to
<a class="reference external" href="https://packaging.python.org/specifications/">https://packaging.python.org/specifications/</a> (similar to what <a class="pep reference internal" href="../pep-0566/" title="PEP 566 Metadata for Python Software Packages 2.1">PEP 566</a> did for
<a class="reference external" href="https://packaging.python.org/specifications/core-metadata/">https://packaging.python.org/specifications/core-metadata/</a>)</li>
<li>updating the PEP itself to focus on the <em>changes</em> being made between the
two versions of the format and the rationale for those changes, rather than
having to repeat all the information that is unchanged from <a class="pep reference internal" href="../pep-0427/" title="PEP 427 The Wheel Binary Package Format 1.0">PEP 427</a></li>
<li>clarifying that the PEP is deliberately written to allow existing installers
to be compliant with the specification when using existing install scheme
definitions, while also allowing the creation of new install scheme
definitions that take advantage of the richer categorisation scheme for
the contents of the binary archive</li>
</ul>
</section>
<section id="rationale">
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
<p>Wheel 1.0 is best at installing files into <code class="docutils literal notranslate"><span class="pre">site-packages</span></code> and a few
other locations specified by distutils, but users would like to install
files from single distribution into many directories perhaps separate
locations for docs, data, and code. Unfortunately not everyone agrees
on where these install locations should be relative to the root directory.
This version of the format adds many more categories, each of which can be
installed to a different destination based on policy. Since it might
also be important to locate the installed files at runtime, this version
of the format also adds a way to record the installed paths in a way that
can be read by the installed software.</p>
</section>
<section id="details">
<h2><a class="toc-backref" href="#details" role="doc-backlink">Details</a></h2>
<section id="installing-a-wheel-distribution-1-0-py32-none-any-whl">
<h3><a class="toc-backref" href="#installing-a-wheel-distribution-1-0-py32-none-any-whl" role="doc-backlink">Installing a wheel distribution-1.0-py32-none-any.whl</a></h3>
<p>Wheel installation notionally consists of two phases:</p>
<ul class="simple">
<li>Unpack.<ol class="loweralpha simple">
<li>Parse <code class="docutils literal notranslate"><span class="pre">distribution-1.0.dist-info/WHEEL</span></code>.</li>
<li>Check that installer is compatible with Wheel-Version. Warn if
minor version is greater, abort if major version is greater.</li>
<li>If Root-Is-Purelib == true, unpack archive into purelib
(site-packages).</li>
<li>Else unpack archive into platlib (site-packages).</li>
</ol>
</li>
<li>Spread.<ol class="loweralpha simple">
<li>Unpacked archive includes <code class="docutils literal notranslate"><span class="pre">distribution-1.0.dist-info/</span></code> and (if
there is data) <code class="docutils literal notranslate"><span class="pre">distribution-1.0.data/</span></code>.</li>
<li>Move each subtree of <code class="docutils literal notranslate"><span class="pre">distribution-1.0.data/</span></code> onto its
destination path. Each subdirectory of <code class="docutils literal notranslate"><span class="pre">distribution-1.0.data/</span></code>
is a key into a dict of destination directories, such as
<code class="docutils literal notranslate"><span class="pre">distribution-1.0.data/(purelib|platlib|headers|scripts|data)</span></code>.</li>
<li>Update scripts starting with <code class="docutils literal notranslate"><span class="pre">#!python</span></code> to point to the correct
interpreter. (Note: Python scripts are usually handled by package
metadata, and not included verbatim in wheel.)</li>
<li>Update <code class="docutils literal notranslate"><span class="pre">distribution-1.0.dist.info/RECORD</span></code> with the installed
paths.</li>
<li>If empty, remove the <code class="docutils literal notranslate"><span class="pre">distribution-1.0.data</span></code> directory.</li>
<li>Compile any installed .py to .pyc. (Uninstallers should be smart
enough to remove .pyc even if it is not mentioned in RECORD.)</li>
</ol>
</li>
</ul>
<p>In practice, installers will usually extract files directly from the archive
to their destinations without writing a temporary <code class="docutils literal notranslate"><span class="pre">distribution-1.0.data/</span></code>
directory.</p>
<section id="recommended-installer-features">
<h4><a class="toc-backref" href="#recommended-installer-features" role="doc-backlink">Recommended installer features</a></h4>
<dl>
<dt>Rewrite <code class="docutils literal notranslate"><span class="pre">#!python</span></code>.</dt><dd>In wheel, verbatim scripts are packaged in
<code class="docutils literal notranslate"><span class="pre">{distribution}-{version}.data/scripts/</span></code>. If the first line of
a file in <code class="docutils literal notranslate"><span class="pre">scripts/</span></code> starts with exactly <code class="docutils literal notranslate"><span class="pre">b'#!python'</span></code>, rewrite to
point to the correct interpreter. Unix installers may need to add
the +x bit to these files if the archive was created on Windows.<p>The <code class="docutils literal notranslate"><span class="pre">b'#!pythonw'</span></code> convention is allowed. <code class="docutils literal notranslate"><span class="pre">b'#!pythonw'</span></code> indicates
a GUI script instead of a console script.</p>
</dd>
<dt>Generate script wrappers.</dt><dd>Python scripts are more commonly represented as a <code class="docutils literal notranslate"><span class="pre">module:callable</span></code>
string in package metadata, and are not included verbatim in the wheel
archives <code class="docutils literal notranslate"><span class="pre">scripts</span></code> directory. This kind of script gives the installer
an opportunity to generate platform specific wrappers.</dd>
</dl>
</section>
<section id="recommended-archiver-features">
<h4><a class="toc-backref" href="#recommended-archiver-features" role="doc-backlink">Recommended archiver features</a></h4>
<dl class="simple">
<dt>Place <code class="docutils literal notranslate"><span class="pre">.dist-info</span></code> at the end of the archive.</dt><dd>Archivers are encouraged to place the <code class="docutils literal notranslate"><span class="pre">.dist-info</span></code> files physically
at the end of the archive. This enables some potentially interesting
ZIP tricks including the ability to amend the metadata without
rewriting the entire archive.</dd>
</dl>
</section>
</section>
<section id="file-format">
<h3><a class="toc-backref" href="#file-format" role="doc-backlink">File Format</a></h3>
<section id="file-name-convention">
<h4><a class="toc-backref" href="#file-name-convention" role="doc-backlink">File name convention</a></h4>
<p>The wheel filename is <code class="docutils literal notranslate"><span class="pre">{distribution}-{version}(-{build</span>
<span class="pre">tag})?-{python</span> <span class="pre">tag}-{abi</span> <span class="pre">tag}-{platform</span> <span class="pre">tag}.whl</span></code>.</p>
<dl class="simple">
<dt>distribution</dt><dd>Distribution name, e.g. django, pyramid.</dd>
<dt>version</dt><dd>Distribution version, e.g. 1.0.</dd>
<dt>build tag</dt><dd>Optional build number. Must start with a digit. A tie breaker
if two wheels have the same version. Sort as the empty string
if unspecified, else sort the initial digits as a number, and the
remainder lexicographically.</dd>
<dt>language implementation and version tag</dt><dd>E.g. py27, py2, py3.</dd>
<dt>abi tag</dt><dd>E.g. cp33m, abi3, none.</dd>
<dt>platform tag</dt><dd>E.g. linux_x86_64, any.</dd>
</dl>
<p>For example, <code class="docutils literal notranslate"><span class="pre">distribution-1.0-1-py27-none-any.whl</span></code> is the first
build of a package called distribution, and is compatible with
Python 2.7 (any Python 2.7 implementation), with no ABI (pure Python),
on any CPU architecture.</p>
<p>The last three components of the filename before the extension are
called “compatibility tags.” The compatibility tags express the
packages basic interpreter requirements and are detailed in <a class="pep reference internal" href="../pep-0425/" title="PEP 425 Compatibility Tags for Built Distributions">PEP 425</a>.</p>
</section>
<section id="escaping-and-unicode">
<h4><a class="toc-backref" href="#escaping-and-unicode" role="doc-backlink">Escaping and Unicode</a></h4>
<p>Each component of the filename is escaped by replacing runs of
non-alphanumeric characters with an underscore <code class="docutils literal notranslate"><span class="pre">_</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="s2">&quot;[^\w\d.]+&quot;</span><span class="p">,</span> <span class="s2">&quot;_&quot;</span><span class="p">,</span> <span class="n">distribution</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">UNICODE</span><span class="p">)</span>
</pre></div>
</div>
<p>The archive filename is Unicode. The packaging tools may only support
ASCII package names, but Unicode filenames are supported in this
specification.</p>
<p>The filenames <em>inside</em> the archive are encoded as UTF-8. Although some
ZIP clients in common use do not properly display UTF-8 filenames,
the encoding is supported by both the ZIP specification and Pythons
<code class="docutils literal notranslate"><span class="pre">zipfile</span></code>.</p>
</section>
<section id="file-contents">
<h4><a class="toc-backref" href="#file-contents" role="doc-backlink">File contents</a></h4>
<p>The contents of a wheel file, where {distribution} is replaced with the
name of the package, e.g. <code class="docutils literal notranslate"><span class="pre">beaglevote</span></code> and {version} is replaced with
its version, e.g. <code class="docutils literal notranslate"><span class="pre">1.0.0</span></code>, consist of:</p>
<ol class="arabic">
<li><code class="docutils literal notranslate"><span class="pre">/</span></code>, the root of the archive, contains all files to be installed in
<code class="docutils literal notranslate"><span class="pre">purelib</span></code> or <code class="docutils literal notranslate"><span class="pre">platlib</span></code> as specified in <code class="docutils literal notranslate"><span class="pre">WHEEL</span></code>. <code class="docutils literal notranslate"><span class="pre">purelib</span></code> and
<code class="docutils literal notranslate"><span class="pre">platlib</span></code> are usually both <code class="docutils literal notranslate"><span class="pre">site-packages</span></code>.</li>
<li><code class="docutils literal notranslate"><span class="pre">{distribution}-{version}.dist-info/</span></code> contains metadata.</li>
<li><code class="docutils literal notranslate"><span class="pre">{distribution}-{version}.data/</span></code> contains one subdirectory
for each non-empty install scheme key not already covered, where
the subdirectory name is an index into a dictionary of install paths
(e.g. <code class="docutils literal notranslate"><span class="pre">data</span></code>, <code class="docutils literal notranslate"><span class="pre">scripts</span></code>, <code class="docutils literal notranslate"><span class="pre">include</span></code>, <code class="docutils literal notranslate"><span class="pre">purelib</span></code>, <code class="docutils literal notranslate"><span class="pre">platlib</span></code>).</li>
<li>Python scripts must appear in <code class="docutils literal notranslate"><span class="pre">scripts</span></code> and begin with exactly
<code class="docutils literal notranslate"><span class="pre">b'#!python'</span></code> in order to enjoy script wrapper generation and
<code class="docutils literal notranslate"><span class="pre">#!python</span></code> rewriting at install time. They may have any or no
extension.</li>
<li><code class="docutils literal notranslate"><span class="pre">{distribution}-{version}.dist-info/METADATA</span></code> is Metadata version 1.1
or greater format metadata.</li>
<li><code class="docutils literal notranslate"><span class="pre">{distribution}-{version}.dist-info/WHEEL</span></code> is metadata about the archive
itself in the same basic key: value format:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Wheel</span><span class="o">-</span><span class="n">Version</span><span class="p">:</span> <span class="mf">1.9</span>
<span class="n">Generator</span><span class="p">:</span> <span class="n">bdist_wheel</span> <span class="mf">1.9</span>
<span class="n">Root</span><span class="o">-</span><span class="n">Is</span><span class="o">-</span><span class="n">Purelib</span><span class="p">:</span> <span class="n">true</span>
<span class="n">Tag</span><span class="p">:</span> <span class="n">py2</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span>
<span class="n">Tag</span><span class="p">:</span> <span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span>
<span class="n">Build</span><span class="p">:</span> <span class="mi">1</span>
<span class="n">Install</span><span class="o">-</span><span class="n">Paths</span><span class="o">-</span><span class="n">To</span><span class="p">:</span> <span class="n">wheel</span><span class="o">/</span><span class="n">_paths</span><span class="o">.</span><span class="n">py</span>
<span class="n">Install</span><span class="o">-</span><span class="n">Paths</span><span class="o">-</span><span class="n">To</span><span class="p">:</span> <span class="n">wheel</span><span class="o">/</span><span class="n">_paths</span><span class="o">.</span><span class="n">json</span>
</pre></div>
</div>
</li>
<li><code class="docutils literal notranslate"><span class="pre">Wheel-Version</span></code> is the version number of the Wheel specification.</li>
<li><code class="docutils literal notranslate"><span class="pre">Generator</span></code> is the name and optionally the version of the software
that produced the archive.</li>
<li><code class="docutils literal notranslate"><span class="pre">Root-Is-Purelib</span></code> is true if the top level directory of the archive
should be installed into purelib; otherwise the root should be installed
into platlib.</li>
<li><code class="docutils literal notranslate"><span class="pre">Tag</span></code> is the wheels expanded compatibility tags; in the example the
filename would contain <code class="docutils literal notranslate"><span class="pre">py2.py3-none-any</span></code>.</li>
<li><code class="docutils literal notranslate"><span class="pre">Build</span></code> is the build number and is omitted if there is no build number.</li>
<li><code class="docutils literal notranslate"><span class="pre">Install-Paths-To</span></code> is a location <em>relative to the archive</em> that will be
overwritten with the install-time paths of each category in the install
scheme. See the install paths section. May appear 0 or more times.</li>
<li>A wheel installer should warn if Wheel-Version is greater than the
version it supports, and must fail if Wheel-Version has a greater
major version than the version it supports.</li>
<li>Wheel, being an installation format that is intended to work across
multiple versions of Python, does not generally include .pyc files.</li>
<li>Wheel does not contain setup.py or setup.cfg.</li>
</ol>
<section id="the-dist-info-directory">
<h5><a class="toc-backref" href="#the-dist-info-directory" role="doc-backlink">The .dist-info directory</a></h5>
<ol class="arabic simple">
<li>Wheel .dist-info directories include at a minimum METADATA, WHEEL,
and RECORD.</li>
<li>METADATA is the package metadata, the same format as PKG-INFO as
found at the root of sdists.</li>
<li>WHEEL is the wheel metadata specific to a build of the package.</li>
<li>RECORD is a list of (almost) all the files in the wheel and their
secure hashes. Unlike <a class="pep reference internal" href="../pep-0376/" title="PEP 376 Database of Installed Python Distributions">PEP 376</a>, every file except RECORD, which
cannot contain a hash of itself, must include its hash. The hash
algorithm must be sha256 or better; specifically, md5 and sha1 are
not permitted, as signed wheel files rely on the strong hashes in
RECORD to validate the integrity of the archive.</li>
<li><a class="pep reference internal" href="../pep-0376/" title="PEP 376 Database of Installed Python Distributions">PEP 376</a>s INSTALLER and REQUESTED are not included in the archive.</li>
<li>RECORD.jws is used for digital signatures. It is not mentioned in
RECORD.</li>
<li>RECORD.p7s is allowed as a courtesy to anyone who would prefer to
use S/MIME signatures to secure their wheel files. It is not
mentioned in RECORD.</li>
<li>During extraction, wheel installers verify all the hashes in RECORD
against the file contents. Apart from RECORD and its signatures,
installation will fail if any file in the archive is not both
mentioned and correctly hashed in RECORD.</li>
</ol>
</section>
<section id="the-data-directory">
<h5><a class="toc-backref" href="#the-data-directory" role="doc-backlink">The .data directory</a></h5>
<p>Any file that is not normally installed inside site-packages goes into
the .data directory, named as the .dist-info directory but with the
.data/ extension:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">distribution</span><span class="o">-</span><span class="mf">1.0</span><span class="o">.</span><span class="n">dist</span><span class="o">-</span><span class="n">info</span><span class="o">/</span>
<span class="n">distribution</span><span class="o">-</span><span class="mf">1.0</span><span class="o">.</span><span class="n">data</span><span class="o">/</span>
</pre></div>
</div>
<p>The .data directory contains subdirectories with the scripts, headers,
documentation and so forth from the distribution. During installation the
contents of these subdirectories are moved onto their destination paths.</p>
<p>If a subdirectory is not found in the install scheme, the installer should
emit a warning, and it should be installed at <code class="docutils literal notranslate"><span class="pre">distribution-1.0.data/...</span></code>
as if the package was unpacked by a standard unzip tool.</p>
</section>
<section id="install-paths">
<h5><a class="toc-backref" href="#install-paths" role="doc-backlink">Install paths</a></h5>
<p>In addition to the distutils install paths, wheel now includes the listed
categories based on GNU autotools. This expanded scheme should help installers
to implement system policy, but installers may root each category at any
location.</p>
<p>A UNIX install scheme might map the categories to their installation paths
like this:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">&#39;bindir&#39;</span><span class="p">:</span> <span class="s1">&#39;$eprefix/bin&#39;</span><span class="p">,</span>
<span class="s1">&#39;sbindir&#39;</span><span class="p">:</span> <span class="s1">&#39;$eprefix/sbin&#39;</span><span class="p">,</span>
<span class="s1">&#39;libexecdir&#39;</span><span class="p">:</span> <span class="s1">&#39;$eprefix/libexec&#39;</span><span class="p">,</span>
<span class="s1">&#39;sysconfdir&#39;</span><span class="p">:</span> <span class="s1">&#39;$prefix/etc&#39;</span><span class="p">,</span>
<span class="s1">&#39;sharedstatedir&#39;</span><span class="p">:</span> <span class="s1">&#39;$prefix/com&#39;</span><span class="p">,</span>
<span class="s1">&#39;localstatedir&#39;</span><span class="p">:</span> <span class="s1">&#39;$prefix/var&#39;</span><span class="p">,</span>
<span class="s1">&#39;libdir&#39;</span><span class="p">:</span> <span class="s1">&#39;$eprefix/lib&#39;</span><span class="p">,</span>
<span class="s1">&#39;static_libdir&#39;</span><span class="p">:</span> <span class="sa">r</span><span class="s1">&#39;$prefix/lib&#39;</span><span class="p">,</span>
<span class="s1">&#39;includedir&#39;</span><span class="p">:</span> <span class="s1">&#39;$prefix/include&#39;</span><span class="p">,</span>
<span class="s1">&#39;datarootdir&#39;</span><span class="p">:</span> <span class="s1">&#39;$prefix/share&#39;</span><span class="p">,</span>
<span class="s1">&#39;datadir&#39;</span><span class="p">:</span> <span class="s1">&#39;$datarootdir&#39;</span><span class="p">,</span>
<span class="s1">&#39;mandir&#39;</span><span class="p">:</span> <span class="s1">&#39;$datarootdir/man&#39;</span><span class="p">,</span>
<span class="s1">&#39;infodir&#39;</span><span class="p">:</span> <span class="s1">&#39;$datarootdir/info&#39;</span><span class="p">,</span>
<span class="s1">&#39;localedir&#39;</span><span class="p">:</span> <span class="s1">&#39;$datarootdir/locale&#39;</span><span class="p">,</span>
<span class="s1">&#39;docdir&#39;</span><span class="p">:</span> <span class="s1">&#39;$datarootdir/doc/$dist_name&#39;</span><span class="p">,</span>
<span class="s1">&#39;htmldir&#39;</span><span class="p">:</span> <span class="s1">&#39;$docdir&#39;</span><span class="p">,</span>
<span class="s1">&#39;dvidir&#39;</span><span class="p">:</span> <span class="s1">&#39;$docdir&#39;</span><span class="p">,</span>
<span class="s1">&#39;psdir&#39;</span><span class="p">:</span> <span class="s1">&#39;$docdir&#39;</span><span class="p">,</span>
<span class="s1">&#39;pdfdir&#39;</span><span class="p">:</span> <span class="s1">&#39;$docdir&#39;</span><span class="p">,</span>
<span class="s1">&#39;pkgdatadir&#39;</span><span class="p">:</span> <span class="s1">&#39;$datadir/$dist_name&#39;</span>
<span class="p">}</span>
</pre></div>
</div>
<p>If a package needs to find its files at runtime, it can request
they be written to a specified file or files by the installer <em>and</em>
included in those same files inside the archive itself, relative
to their location within the archive (so a wheel is still installed
correctly if unpacked with a standard unzip tool, or perhaps not
unpacked at all).</p>
<p>If the <code class="docutils literal notranslate"><span class="pre">WHEEL</span></code> metadata contains these fields:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">Install</span><span class="o">-</span><span class="n">Paths</span><span class="o">-</span><span class="n">To</span><span class="p">:</span> <span class="n">wheel</span><span class="o">/</span><span class="n">_paths</span><span class="o">.</span><span class="n">py</span>
<span class="n">Install</span><span class="o">-</span><span class="n">Paths</span><span class="o">-</span><span class="n">To</span><span class="p">:</span> <span class="n">wheel</span><span class="o">/</span><span class="n">_paths</span><span class="o">.</span><span class="n">json</span>
</pre></div>
</div>
<p>Then the wheel installer, when it is about to unpack <code class="docutils literal notranslate"><span class="pre">wheel/_paths.py</span></code> from
the archive, replaces it with the actual paths used at install time. The
paths may be absolute or relative to the generated file.</p>
<p>If the filename ends with <code class="docutils literal notranslate"><span class="pre">.py</span></code> then a Python script is written. The
script MUST be executed to get the paths, but it will probably look like
this:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">data</span><span class="o">=</span><span class="s1">&#39;../wheel-0.26.0.dev1.data/data&#39;</span>
<span class="n">headers</span><span class="o">=</span><span class="s1">&#39;../wheel-0.26.0.dev1.data/headers&#39;</span>
<span class="n">platlib</span><span class="o">=</span><span class="s1">&#39;../wheel-0.26.0.dev1.data/platlib&#39;</span>
<span class="n">purelib</span><span class="o">=</span><span class="s1">&#39;../wheel-0.26.0.dev1.data/purelib&#39;</span>
<span class="n">scripts</span><span class="o">=</span><span class="s1">&#39;../wheel-0.26.0.dev1.data/scripts&#39;</span>
<span class="c1"># ...</span>
</pre></div>
</div>
<p>If the filename ends with <code class="docutils literal notranslate"><span class="pre">.json</span></code> then a JSON document is written:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">{</span> <span class="s2">&quot;data&quot;</span><span class="p">:</span> <span class="s2">&quot;../wheel-0.26.0.dev1.data/data&quot;</span><span class="p">,</span> <span class="o">...</span> <span class="p">}</span>
</pre></div>
</div>
<p>Only the categories actually used by a particular wheel must be written to
this file.</p>
<p>These files are designed to be written to a location that can be found by the
installed package without introducing any dependency on a packaging library.</p>
</section>
</section>
</section>
<section id="signed-wheel-files">
<h3><a class="toc-backref" href="#signed-wheel-files" role="doc-backlink">Signed wheel files</a></h3>
<p>Wheel files include an extended RECORD that enables digital
signatures. <a class="pep reference internal" href="../pep-0376/" title="PEP 376 Database of Installed Python Distributions">PEP 376</a>s RECORD is altered to include a secure hash
<code class="docutils literal notranslate"><span class="pre">digestname=urlsafe_b64encode_nopad(digest)</span></code> (urlsafe base64
encoding with no trailing = characters) as the second column instead
of an md5sum. All possible entries are hashed, including any
generated files such as .pyc files, but not RECORD which cannot contain its
own hash. For example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">file</span><span class="o">.</span><span class="n">py</span><span class="p">,</span><span class="n">sha256</span><span class="o">=</span><span class="n">AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT</span>\<span class="n">_pNh2yI</span><span class="p">,</span><span class="mi">3144</span>
<span class="n">distribution</span><span class="o">-</span><span class="mf">1.0</span><span class="o">.</span><span class="n">dist</span><span class="o">-</span><span class="n">info</span><span class="o">/</span><span class="n">RECORD</span><span class="p">,,</span>
</pre></div>
</div>
<p>The signature file(s) RECORD.jws and RECORD.p7s are not mentioned in
RECORD at all since they can only be added after RECORD is generated.
Every other file in the archive must have a correct hash in RECORD
or the installation will fail.</p>
<p>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 signatures JSON payload:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">{</span> <span class="s2">&quot;hash&quot;</span><span class="p">:</span> <span class="s2">&quot;sha256=ADD-r2urObZHcxBW3Cr-vDCu5RJwT4CaRTHiFmbcIYY&quot;</span> <span class="p">}</span>
</pre></div>
</div>
<p>(The hash value is the same format used in RECORD.)</p>
<p>If RECORD.p7s is used, it must contain a detached S/MIME format signature
of RECORD.</p>
<p>A wheel installer is not required to understand digital signatures but
MUST verify the hashes in RECORD against the extracted file contents.
When the installer checks file hashes against RECORD, a separate signature
checker only needs to establish that RECORD matches the signature.</p>
<p>See</p>
<ul class="simple">
<li><span class="target" id="index-0"></span><a class="rfc reference external" href="https://datatracker.ietf.org/doc/html/rfc7515.html"><strong>RFC 7515</strong></a></li>
<li><a class="reference external" href="https://datatracker.ietf.org/doc/html/draft-jones-jose-jws-json-serialization.html">https://datatracker.ietf.org/doc/html/draft-jones-jose-jws-json-serialization.html</a></li>
<li><span class="target" id="index-1"></span><a class="rfc reference external" href="https://datatracker.ietf.org/doc/html/rfc7517.html"><strong>RFC 7517</strong></a></li>
<li><a class="reference external" href="https://datatracker.ietf.org/doc/html/draft-jones-jose-json-private-key.html">https://datatracker.ietf.org/doc/html/draft-jones-jose-json-private-key.html</a></li>
</ul>
</section>
<section id="comparison-to-egg">
<h3><a class="toc-backref" href="#comparison-to-egg" role="doc-backlink">Comparison to .egg</a></h3>
<ol class="arabic simple">
<li>Wheel is an installation format; egg is importable. Wheel archives
do not need to include .pyc and are less tied to a specific Python
version or implementation. Wheel can install (pure Python) packages
built with previous versions of Python so you dont always have to
wait for the packager to catch up.</li>
<li>Wheel uses .dist-info directories; egg uses .egg-info. Wheel is
compatible with the new world of Python packaging and the new
concepts it brings.</li>
<li>Wheel has a richer file naming convention for todays
multi-implementation world. A single wheel archive can indicate
its compatibility with a number of Python language versions and
implementations, ABIs, and system architectures. Historically the
ABI has been specific to a CPython release, wheel is ready for the
stable ABI.</li>
<li>Wheel is lossless. The first wheel implementation bdist_wheel
always generates egg-info, and then converts it to a .whl. It is
also possible to convert existing eggs and bdist_wininst
distributions.</li>
<li>Wheel is versioned. Every wheel file contains the version of the
wheel specification and the implementation that packaged it.
Hopefully the next migration can simply be to Wheel 2.0.</li>
<li>Wheel is a reference to the other Python.</li>
</ol>
</section>
</section>
<section id="faq">
<h2><a class="toc-backref" href="#faq" role="doc-backlink">FAQ</a></h2>
<section id="wheel-defines-a-data-directory-should-i-put-all-my-data-there">
<h3><a class="toc-backref" href="#wheel-defines-a-data-directory-should-i-put-all-my-data-there" role="doc-backlink">Wheel defines a .data directory. Should I put all my data there?</a></h3>
<blockquote>
<div>This specification does not have an opinion on how you should organize
your code. The .data directory is just a place for any files that are
not normally installed inside <code class="docutils literal notranslate"><span class="pre">site-packages</span></code> or on the PYTHONPATH.
In other words, you may continue to use <code class="docutils literal notranslate"><span class="pre">pkgutil.get_data(package,</span>
<span class="pre">resource)</span></code> even though <em>those</em> files will usually not be distributed
in <em>wheels</em> <code class="docutils literal notranslate"><span class="pre">.data</span></code> directory.</div></blockquote>
</section>
<section id="why-does-wheel-include-attached-signatures">
<h3><a class="toc-backref" href="#why-does-wheel-include-attached-signatures" role="doc-backlink">Why does wheel include attached signatures?</a></h3>
<blockquote>
<div>Attached signatures are more convenient than detached signatures
because they travel with the archive. Since only the individual files
are signed, the archive can be recompressed without invalidating
the signature or individual files can be verified without having
to download the whole archive.</div></blockquote>
</section>
<section id="why-does-wheel-allow-jws-signatures">
<h3><a class="toc-backref" href="#why-does-wheel-allow-jws-signatures" role="doc-backlink">Why does wheel allow JWS signatures?</a></h3>
<blockquote>
<div>The JOSE specifications of which JWS is a part are designed to be easy
to implement, a feature that is also one of wheels primary design
goals. JWS yields a useful, concise pure-Python implementation.</div></blockquote>
</section>
<section id="why-does-wheel-also-allow-s-mime-signatures">
<h3><a class="toc-backref" href="#why-does-wheel-also-allow-s-mime-signatures" role="doc-backlink">Why does wheel also allow S/MIME signatures?</a></h3>
<blockquote>
<div>S/MIME signatures are allowed for users who need or want to use
existing public key infrastructure with wheel.<p>Signed packages are only a basic building block in a secure package
update system. Wheel only provides the building block.</p>
</div></blockquote>
</section>
<section id="what-s-the-deal-with-purelib-vs-platlib">
<h3><a class="toc-backref" href="#what-s-the-deal-with-purelib-vs-platlib" role="doc-backlink">Whats the deal with “purelib” vs. “platlib”?</a></h3>
<blockquote>
<div>Wheel preserves the “purelib” vs. “platlib” distinction, which is
significant on some platforms. For example, Fedora installs pure
Python packages to /usr/lib/pythonX.Y/site-packages and platform
dependent packages to /usr/lib64/pythonX.Y/site-packages.<p>A wheel with “Root-Is-Purelib: false” with all its files
in <code class="docutils literal notranslate"><span class="pre">{name}-{version}.data/purelib</span></code> is equivalent to a wheel with
“Root-Is-Purelib: true” with those same files in the root, and it
is legal to have files in both the “purelib” and “platlib” categories.</p>
<p>In practice a wheel should have only one of “purelib” or “platlib”
depending on whether it is pure Python or not and those files should
be at the root with the appropriate setting given for “Root-is-purelib”.</p>
</div></blockquote>
</section>
<section id="is-it-possible-to-import-python-code-directly-from-a-wheel-file">
<h3><a class="toc-backref" href="#is-it-possible-to-import-python-code-directly-from-a-wheel-file" role="doc-backlink">Is it possible to import Python code directly from a wheel file?</a></h3>
<blockquote>
<div>Technically, due to the combination of supporting installation via
simple extraction and using an archive format that is compatible with
<code class="docutils literal notranslate"><span class="pre">zipimport</span></code>, a subset of wheel files <em>do</em> support being placed directly
on <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>. However, while this behaviour is a natural consequence
of the format design, actually relying on it is generally discouraged.<p>Firstly, wheel <em>is</em> designed primarily as a distribution format, so
skipping the installation step also means deliberately avoiding any
reliance on features that assume full installation (such as being able
to use standard tools like <code class="docutils literal notranslate"><span class="pre">pip</span></code> and <code class="docutils literal notranslate"><span class="pre">virtualenv</span></code> to capture and
manage dependencies in a way that can be properly tracked for auditing
and security update purposes, or integrating fully with the standard
build machinery for C extensions by publishing header files in the
appropriate place).</p>
<p>Secondly, while some Python software is written to support running
directly from a zip archive, it is still common for code to be written
assuming it has been fully installed. When that assumption is broken
by trying to run the software from a zip archive, the failures can often
be obscure and hard to diagnose (especially when they occur in third
party libraries). The two most common sources of problems with this
are the fact that importing C extensions from a zip archive is <em>not</em>
supported by CPython (since doing so is not supported directly by the
dynamic loading machinery on any platform) and that when running from
a zip archive the <code class="docutils literal notranslate"><span class="pre">__file__</span></code> attribute no longer refers to an
ordinary filesystem path, but to a combination path that includes
both the location of the zip archive on the filesystem and the
relative path to the module inside the archive. Even when software
correctly uses the abstract resource APIs internally, interfacing with
external components may still require the availability of an actual
on-disk file.</p>
<p>Like metaclasses, monkeypatching and metapath importers, if youre not
already sure you need to take advantage of this feature, you almost
certainly dont need it. If you <em>do</em> decide to use it anyway, be
aware that many projects will require a failure to be reproduced with
a fully installed package before accepting it as a genuine bug.</p>
</div></blockquote>
</section>
</section>
<section id="appendix">
<h2><a class="toc-backref" href="#appendix" role="doc-backlink">Appendix</a></h2>
<p>Example urlsafe-base64-nopad implementation:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># urlsafe-base64-nopad for Python 3</span>
<span class="kn">import</span> <span class="nn">base64</span>
<span class="k">def</span> <span class="nf">urlsafe_b64encode_nopad</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="k">return</span> <span class="n">base64</span><span class="o">.</span><span class="n">urlsafe_b64encode</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="sa">b</span><span class="s1">&#39;=&#39;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">urlsafe_b64decode_nopad</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="n">pad</span> <span class="o">=</span> <span class="sa">b</span><span class="s1">&#39;=&#39;</span> <span class="o">*</span> <span class="p">(</span><span class="mi">4</span> <span class="o">-</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mi">3</span><span class="p">))</span>
<span class="k">return</span> <span class="n">base64</span><span class="o">.</span><span class="n">urlsafe_b64decode</span><span class="p">(</span><span class="n">data</span> <span class="o">+</span> <span class="n">pad</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="copyright">
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
<p>This document has been placed into the public domain.</p>
</section>
</section>
<hr class="docutils" />
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0491.rst">https://github.com/python/peps/blob/main/peps/pep-0491.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0491.rst">2023-09-09 17:39:29 GMT</a></p>
</article>
<nav id="pep-sidebar">
<h2>Contents</h2>
<ul>
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#pep-deferral">PEP Deferral</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#details">Details</a><ul>
<li><a class="reference internal" href="#installing-a-wheel-distribution-1-0-py32-none-any-whl">Installing a wheel distribution-1.0-py32-none-any.whl</a><ul>
<li><a class="reference internal" href="#recommended-installer-features">Recommended installer features</a></li>
<li><a class="reference internal" href="#recommended-archiver-features">Recommended archiver features</a></li>
</ul>
</li>
<li><a class="reference internal" href="#file-format">File Format</a><ul>
<li><a class="reference internal" href="#file-name-convention">File name convention</a></li>
<li><a class="reference internal" href="#escaping-and-unicode">Escaping and Unicode</a></li>
<li><a class="reference internal" href="#file-contents">File contents</a><ul>
<li><a class="reference internal" href="#the-dist-info-directory">The .dist-info directory</a></li>
<li><a class="reference internal" href="#the-data-directory">The .data directory</a></li>
<li><a class="reference internal" href="#install-paths">Install paths</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#signed-wheel-files">Signed wheel files</a></li>
<li><a class="reference internal" href="#comparison-to-egg">Comparison to .egg</a></li>
</ul>
</li>
<li><a class="reference internal" href="#faq">FAQ</a><ul>
<li><a class="reference internal" href="#wheel-defines-a-data-directory-should-i-put-all-my-data-there">Wheel defines a .data directory. Should I put all my data there?</a></li>
<li><a class="reference internal" href="#why-does-wheel-include-attached-signatures">Why does wheel include attached signatures?</a></li>
<li><a class="reference internal" href="#why-does-wheel-allow-jws-signatures">Why does wheel allow JWS signatures?</a></li>
<li><a class="reference internal" href="#why-does-wheel-also-allow-s-mime-signatures">Why does wheel also allow S/MIME signatures?</a></li>
<li><a class="reference internal" href="#what-s-the-deal-with-purelib-vs-platlib">Whats the deal with “purelib” vs. “platlib”?</a></li>
<li><a class="reference internal" href="#is-it-possible-to-import-python-code-directly-from-a-wheel-file">Is it possible to import Python code directly from a wheel file?</a></li>
</ul>
</li>
<li><a class="reference internal" href="#appendix">Appendix</a></li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
<br>
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0491.rst">Page Source (GitHub)</a>
</nav>
</section>
<script src="../_static/colour_scheme.js"></script>
<script src="../_static/wrap_tables.js"></script>
<script src="../_static/sticky_banner.js"></script>
</body>
</html>