Skip to content

Commit e2f388c

Browse files
emmatyping-nvJelleZijlstrahugovkwillingc
authored andcommitted
PEP 777: How to Re-invent the Wheel (python#4036)
Co-authored-by: Jelle Zijlstra <[email protected]> Co-authored-by: Hugo van Kemenade <[email protected]> Co-authored-by: Carol Willing <[email protected]>
1 parent 7375dda commit e2f388c

File tree

5 files changed

+383
-0
lines changed

5 files changed

+383
-0
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,8 @@ peps/pep-0759.rst @warsaw
640640
peps/pep-0760.rst @pablogsal @brettcannon
641641
peps/pep-0761.rst @sethmlarson @hugovk
642642
# ...
643+
peps/pep-0777.rst @warsaw
644+
# ...
643645
peps/pep-0789.rst @njsmith
644646
# ...
645647
peps/pep-0801.rst @warsaw

peps/pep-0777.rst

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
PEP: 777
2+
Title: How to Re-invent the Wheel
3+
Author: Ethan Smith <[email protected]>
4+
Sponsor: Barry Warsaw <[email protected]>
5+
PEP-Delegate: Paul Moore <[email protected]>
6+
Status: Draft
7+
Type: Standards Track
8+
Topic: Packaging
9+
Created: 09-Oct-2024
10+
Post-History:
11+
12+
Abstract
13+
========
14+
15+
The current :pep:`wheel 1.0 specification <427>` was written over a decade ago,
16+
and has been extremely robust to changes in the Python packaging ecosystem.
17+
Previous efforts to improve the wheel specification
18+
:pep:`were deferred <491#pep-deferral>` to focus on other packaging
19+
specifications. Meanwhile, the use of wheels has changed dramatically in the
20+
last decade. There have been many requests for new wheel features over the
21+
years; however, a fundamental obstacle to evolving the wheel specification has
22+
been that there is no defined process for how to handle adding
23+
backwards-incompatible features to wheels. Therefore, to enable other PEPs to
24+
describe new enhancements to the wheel specification, **this PEP prescribes**
25+
**compatibility requirements on future wheel revisions**. This PEP does *not*
26+
specify a new wheel revision. The specification of a new wheel format
27+
(“Wheel 2.0”) is left to a future PEP.
28+
29+
Rationale
30+
=========
31+
32+
Currently, wheel specification changes that require new installer behavior are backwards incompatible and require a major version increase in
33+
the wheel metadata format. An increase of the wheel major version has yet to
34+
happen, partially because such a change has the potential to be
35+
catastrophically disruptive. Per
36+
`the wheel specification <https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl>`_,
37+
any installer that does not support the new major version must abort at install
38+
time. This means that if the major version were to be incremented without
39+
further planning, many users would see installation failures as older installers reject new wheels
40+
uploaded to public package indices like the Python Package Index (PyPI). It is
41+
critically important to carefully plan the interactions between build tools,
42+
package indices, and package installers to avoid incompatibility issues,
43+
especially considering the long tail of users who are slow to update their
44+
installers.
45+
46+
The backward compatibility concerns have prevented valuable improvements
47+
to the wheel file format, such as
48+
`better compression <https://discuss.python.org/t/improving-wheel-compression-by-nesting-data-as-a-second-zip/1747>`_,
49+
`wheel data format improvements <https://discuss.python.org/t/should-there-be-a-new-standard-for-installing-arbitrary-data-files/7853/7>`_,
50+
`better information about what is included in a wheel <https://discuss.python.org/t/record-the-top-level-names-of-a-wheel-in-metadata/29494>`_,
51+
and `JSON formatted metadata in the ".dist-info" folder <https://discuss.python.org/t/is-was-there-a-goal-with-pep-566s-json-encoding-section/12324/3>`_.
52+
53+
This PEP describes constraints and behavior for new wheel revisions to preserve
54+
stability for existing tools that do not support a new major version of the wheel format.
55+
This ensures that backwards incompatible changes to the wheel specification
56+
will only affect users and tools that are properly set up to use the newer
57+
wheels. With a clear path for evolving the wheel specification, future PEPs
58+
will be able to improve the wheel format without needing to re-define a
59+
completely new compatibility story.
60+
61+
Specification
62+
=============
63+
64+
Add Wheel-Version Metadata Field to Core Metadata
65+
-------------------------------------------------
66+
67+
Currently, the :pep:`wheel 1.0 PEP <427>`, PEP 427, specifies that wheel files
68+
must contain a ``WHEEL`` metadata file that contains the version of the wheel
69+
specification that the file conforms to. PEP 427 stipulates that installers
70+
MUST warn on installation of a wheel with a minor version greater than supported,
71+
and MUST abort on installation of wheels with a major version that is greater than
72+
what the installer supports. This ensures that users do not get invalid
73+
installations from wheels that installers cannot properly install.
74+
75+
However, resolvers do not currently exclude wheels with an incompatible wheel
76+
version. There is also currently no way for a resolver to check a wheel's
77+
version without downloading the wheel directly. To make wheel version filtering
78+
easy for resolvers, the wheel version **MUST** be included in the relevant
79+
metadata file (currently METADATA). This will allow resolvers to efficiently
80+
check the wheel version using the :pep:`658` metadata API without needing to
81+
download and inspect the ``.dist-info/WHEEL`` file.
82+
83+
To accomplish this, a new core metadata field is introduced called
84+
``Wheel-Version``. While this field is optional for metadata included in a
85+
wheel of major version 1, it is a mandatory field for metadata in wheels of major
86+
version 2 or higher. This enforces that future revisions of the wheel
87+
specification can rely on resolvers skipping incompatible wheels by checking
88+
the ``Wheel-Version`` field.
89+
90+
The ``Wheel-Version`` field in the metadata file shall contain the exact same entry as the
91+
``Wheel-Version`` entry in the ``WHEEL`` file, or any future replacement file
92+
defining metadata about the wheel file. Installers **MUST** verify that these
93+
entries match when installing a wheel. If ``Wheel-Version`` is absent from the
94+
metadata file, then the implied major version of the wheel is 1.
95+
96+
Resolver Behavior Regarding ``Wheel-Version``
97+
---------------------------------------------
98+
99+
Resolvers, in the process of selecting a wheel to install, **MUST** check a
100+
candidate wheel's ``Wheel-Version``, and ignore incompatible wheel files.
101+
Without ignoring these files, older installers might select a wheel that uses
102+
an unsupported wheel version for that installer, and force the installer to
103+
abort per :pep:`427`. By skipping incompatible wheel files, users will not see
104+
installation errors when a project adopts a new wheel major version. As already
105+
specified in PEP 427, installers **MUST** abort if a user tries to directly
106+
install a wheel that is incompatible. If, in the process of resolving packages
107+
found in multiple indices, a resolver comes across two wheels of the same
108+
distribution and version, resolvers should prioritize the wheel of the highest
109+
compatible version.
110+
111+
While the above protects users from unexpected breakages, users may miss a new
112+
release of a distribution if their installer does not support the wheel version
113+
used in the release. Imagine in the future that a package publishes 3.0 wheel
114+
files. Downstream users won't see that there is a new release available if
115+
their installers only support 2.x wheels. Therefore, installers **SHOULD** emit
116+
a warning if, in the process of resolving packages, they come across an incompatible wheel
117+
and skip it.
118+
119+
First Major Version Bump Must Change File Extension
120+
---------------------------------------------------
121+
122+
Unfortunately, existing resolvers do not check the compatibility of wheels
123+
before selecting them as installation candidates. Until a majority of users
124+
update to installers that properly check for wheel compatibility, it is unsafe
125+
to allow publishing wheels of a new major version that existing resolvers might
126+
select. It could take upwards of four years before the majority of users are on
127+
updated resolvers, based on current data about PyPI installer usage (See the
128+
:ref:`777-pypi-download-analysis`, for
129+
details). To allow for experimentation and faster adoption of 2.0 wheels,
130+
this PEP proposes a one time change to the file extension of the
131+
wheel file format, from ``.whl`` to ``.whlx``. This resolves the initial
132+
transition issue of 2.0 wheels breaking users on existing installers that do
133+
not implement ``Wheel-Version`` checks. By using a different file extension,
134+
2.0 wheels can immediately be uploaded to PyPI, and users will be able to
135+
experiment with the new features right away. Users on older installers will
136+
simply ignore these new files.
137+
138+
One rejected alternative would be to keep the ``.whl`` extension, but delay the
139+
publishing of wheel 2.0 to PyPI. For more on that, please see Rejected Ideas.
140+
141+
Recommended Build Backend Behavior with New Wheel Formats
142+
---------------------------------------------------------
143+
144+
Build backends are recommended to generate the most compatible wheel based on
145+
features a project uses. For example, if a wheel does not use symbolic links,
146+
and such a feature was introduced in wheel 5.0, the build backend could
147+
generate a wheel of version 4.0. On the other hand, some features will want to
148+
be adopted by default. For example, if wheel 3.0 introduces better compression,
149+
the build backend may wish to enable this feature by default to improve the
150+
wheel size and download performance.
151+
152+
Limitations on Future Wheel Revisions
153+
-------------------------------------
154+
155+
While it is difficult to know what future features may be planned for the wheel
156+
format, it is important that certain compatibility promises are maintained.
157+
158+
Wheel files, when installed, **MUST** stay compatible with the Python standard
159+
library's ``importlib.metadata`` for all supported CPython versions. For
160+
example, replacing ``.dist-info/METADATA`` with a JSON formatted metadata file
161+
MUST be a multi-major version migration with one version introducing the new
162+
JSON file alongside the existing email header format, and another future
163+
version removing the email header format metadata file. The version to remove
164+
``.dist-info/METADATA`` also **MUST** be adopted only after the last CPython
165+
release that lacked support for the new file reaches end of life. This ensures
166+
that code using ``importlib.metadata`` will not break with wheel major version
167+
revisions.
168+
169+
Wheel files **MUST** remain ZIP format files as the outer container format.
170+
Additionally, the ``.dist-info`` metadata directory **MUST** be placed at the
171+
root of the archive without any compression, so that unpacking the wheel file
172+
produces a normal ``.dist-info`` directory holding any metadata for the wheel.
173+
Future wheel revisions **MAY** modify the layout, compression, and other
174+
attributes about non-metadata components of a wheel such as data and code. This
175+
assures that future wheel revisions remain compatible with tools operating on
176+
package metadata, while allowing for improvements to code storage in the wheel,
177+
such as adopting compression.
178+
179+
Package tooling **MUST NOT** assume that the contents and format of the wheel
180+
file will remain the same for future wheel major versions beyond the
181+
limitations above about metadata folder contents and outer container format.
182+
For example, newer wheel major versions may add or remove filename components,
183+
such as the build tag or the platform tag. Therefore it is incumbent upon
184+
tooling to check the metadata for the ``Wheel-Version`` before attempting to
185+
install a wheel.
186+
187+
Finally, future wheel revisions **MUST NOT** use any compression formats not in
188+
the CPython standard library of at least the latest release. Wheels generated
189+
using any new compression format should be tagged as requiring at least the
190+
first released version of CPython to support the new compression format,
191+
regardless of the Python API compatibility of the code within the wheel.
192+
193+
Backwards Compatibility
194+
=======================
195+
196+
Backwards compatibility is an incredibly important issue for evolving the wheel
197+
format. If adopting a new wheel revision is painful for downstream users,
198+
package creators will hesitate to adopt the new standards, and users will be
199+
stuck with failed CI pipelines and other installation woes.
200+
201+
Several choices in the above specification are made so that the adoption of a
202+
new feature is less painful. For example, today wheels of an incompatible major
203+
version are still selected by pip as installation candidates, which causes
204+
installer failures if a project starts publishing 2.0 wheels. To avoid this
205+
issue, this PEP requires resolvers to filter out wheels with major versions or
206+
features incompatible with the installer.
207+
208+
This PEP also defines constraints on future wheel revisions, with the goal of
209+
maintaining compatibility with CPython, but allowing evolution of wheel
210+
contents. Wheel revisions shouldn't cause package installations to break on
211+
older CPython revisions, as not only would it be frustrating, it would be
212+
incredibly hard to debug for users.
213+
214+
The main compatibility limitation of this PEP is for projects that start
215+
publishing solely new wheels alongside a source distribution. If a user on an
216+
older installer tries to install the package, it will fall back to the source
217+
distribution, because the resolver will skip all newer wheels. Users are often
218+
poorly set up to build projects from source, so this could lead to some failed
219+
builds users would not see otherwise. There are several approaches to resolving
220+
this issue, such as allowing dual-publishing for the initial migration, or
221+
marking source distributions as not intended to be built.
222+
223+
Rejected Ideas
224+
==============
225+
226+
The Wheel Format is Perfect and Does not Need to be Changed
227+
-----------------------------------------------------------
228+
The wheel format has been around for over 10 years, and in that time, Python
229+
packages have changed a lot. It is much more common for packages to include
230+
Rust or C extension modules, increasing the size of packages. Better
231+
compression, such as lzma or zstd, could save a lot of time and bandwidth for
232+
PyPI and its users. Compatibility tags cannot express the wide variety of
233+
hardware used to accelerate Python code today, nor encode shared library
234+
compatibility information. In order to address these issues, evolution of the
235+
wheel package format is necessary.
236+
237+
Wheel Format Changes Should be Tied to CPython Releases
238+
-------------------------------------------------------
239+
I do not believe that tying wheel revisions to CPython
240+
releases is beneficial. The main benefit of doing so is to make adoption of new
241+
wheels predictable - users with the latest CPython get the latest package
242+
format! This choice has several issues however. First, tying the new format
243+
to the latest CPython makes adoption much slower. Users on LTS versions of
244+
Linux with older Python installations are free to update their pip in a virtual
245+
environment, but cannot update the version of Python as easily. While some
246+
changes to the wheel format must be tied to CPython changes necessarily, such
247+
as adding new compression formats or changing the metadata format, many changes
248+
do not need to be tied to the Python version, such as symlinks, enhanced
249+
compatibility tags, and new formats that use existing compression formats in
250+
the standard library. Additionally, wheels are used across multiple different
251+
language implementations, which lag behind the CPython version. It seems unfair
252+
to prevent their users from using a feature due to the Python version. Lastly,
253+
while this PEP does not suggest tying the wheel version to CPython releases, a
254+
future PEP may still do so at any time, so this choice does not need to be made
255+
in this PEP.
256+
257+
Keep Using ``.whl`` as the File Extension
258+
-----------------------------------------
259+
While keeping the extension ``.whl`` is appealing for many reasons, it presents
260+
several problems that are difficult to surmount. First, current installers
261+
would still pick a new wheel and fail to install the package. Furthermore,
262+
the file name of a wheel would not be able to change without breaking existing
263+
installers that expect a set wheel file name format. While the current filename
264+
specification for wheels is sufficient for current usage, the optional
265+
build tag in the middle of the file name makes any extensions ambiguous (i.e.
266+
``foo-0.3-py3-none-any-fancy_new_tag.whl`` would parse as the build tag being
267+
``py3``). This limits changes to information stored in the wheel file name.
268+
269+
Discussion Topics
270+
=================
271+
272+
Should Indices Support Dual-publishing for the First Migration?
273+
---------------------------------------------------------------
274+
Since ``.whl`` and ``.whlx`` will look different in file name, they could be
275+
uploaded side-by-side to package indices like PyPI. This has some nice
276+
benefits, like dual-support for older and newer installers, so users who can
277+
get the latest features, while users who don't upgrade still can install the
278+
latest version of a package.
279+
280+
There are many complications however. Should we allow wheel 2 uploads to
281+
existing wheel 1-only releases? Should we put any requirements on the
282+
side-by-side wheels, such as:
283+
284+
.. admonition:: Constraints on dual-published wheels
285+
286+
A given index may contain identical-content wheels with different wheel
287+
versions, and installers should prefer the newest-available wheel format,
288+
with all other factors held constant.
289+
290+
Should we only allow uploading both with :pep:`694` allowing "atomic"
291+
dual-publishing?
292+
293+
Acknowledgements
294+
================
295+
296+
The author of this PEP is greatly indebted to the incredibly valuable review,
297+
advice, and feedback of Barry Warsaw and Michael Sarahan.
298+
299+
Copyright
300+
=========
301+
302+
This document is placed in the public domain or under the
303+
CC0-1.0-Universal license, whichever is more permissive.
117 KB
Loading
126 KB
Loading

0 commit comments

Comments
 (0)