Skip to content

Commit ddf1f9f

Browse files
committed
PEP: Project status markers in the simple index
Signed-off-by: William Woodruff <william@yossarian.net>
1 parent 5625a11 commit ddf1f9f

1 file changed

Lines changed: 371 additions & 0 deletions

File tree

peps/pep-9999.rst

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
PEP: 9999
2+
Title: Project status markers in the simple index
3+
Author: William Woodruff <william@yossarian.net>,
4+
Facundo Tuesca <facundo.tuesca@trailofbits.com>
5+
Sponsor: Donald Stufft <donald@stufft.io>
6+
PEP-Delegate: Donald Stufft <donald@stufft.io>
7+
Discussions-To: https://discuss.python.org/t/79356
8+
Status: Draft
9+
Type: Standards Track
10+
Topic: Packaging
11+
Created: 08-Apr-2025
12+
Post-History: `03-Feb-2025 <https://discuss.python.org/t/79356/>`__
13+
14+
Abstract
15+
========
16+
17+
This PEP proposes a standardized set of project status markers, as well
18+
as a mechanism for communicating those markers in the HTML and JSON
19+
simple indices.
20+
21+
Rationale and Motivation
22+
========================
23+
24+
The "status" of a project is an important piece of metadata, made more important
25+
by growth in both the breadth and depth of the Python packaging ecosystem.
26+
Both humans and tools use project statuses (or proxies for status,
27+
such as recent activity) to determine whether the project is maintained or
28+
otherwise suitable for consumption.
29+
30+
Python packaging has at least three different mechanisms for communicating
31+
the "status" of a project:
32+
33+
1. Distributions can include *trove classifiers* in their metadata, as
34+
originally specified in :pep:`301`. The list of supported classifiers is
35+
`maintained by the PyPA <https://github.com/pypa/trove-classifiers>`_,
36+
and includes the ``Development Status`` hierarchy. For example, a
37+
distribution can include the ``Development Status :: 7 - Inactive``
38+
classifier to indicate that the distribution's project is inactive.
39+
40+
Trove classifiers are flexible, but also come with significant limitations:
41+
they're machine-readable and are rendered on indices like PyPI, but
42+
they also require the maintainer to push one or more *new* distributions
43+
each time they wish to update their project's development status.
44+
Furthermore, because distributions are *de facto* immutable in the Python
45+
packaging ecosystem, older distributions can't have their classifiers
46+
updated to reflect the current status of the project.
47+
48+
2. Indices can mark distributions and releases as "yanked", as originally
49+
specified in :pep:`592`. Yanked distributions are not considered
50+
eligible for dependency resolution.
51+
52+
When a distribution has been yanked, it is marked with ``data-yanked``
53+
in the HTML index and with ``yanked: bool | str`` in the JSON index.
54+
Additionally, indices like PyPI will hide yanked distributions by default
55+
and will render them with a notice when the user navigates directly to them.
56+
57+
Yanking is machine-readable like trove classifiers, but is single-purpose
58+
rather than general-purpose: users can specify a free-form "yank reason,"
59+
but the semantics of yanking are fixed.
60+
61+
3. PyPI itself has *project statuses*, which apply to the entire project
62+
(i.e., all releases and distributions). Project statuses have both
63+
maintainer- and index-admin-controllable states:
64+
65+
* PyPI administrators can "quarantine" a project. Quarantine behaves like
66+
a strengthened yank: the project remains entirely uninstallable while
67+
quarantined.
68+
69+
* Project maintainers can "archive" a project. Archiving a project
70+
disables new release and distribution uploads to that project,
71+
but otherwise has no effect on the project's downloadability.
72+
73+
Project statuses are machine-readable *in principle*, but are not currently
74+
exposed via any of PyPI's APIs. Instead, PyPI renders project statuses on
75+
each project's user-facing (i.e. non-index) HTML.
76+
77+
In summary: there are multiple ways to communicate the "status" of a project in
78+
Python packaging, but none of these mechanisms are *simultaneously* (1)
79+
machine-readable, (2) general, (3) index-agnostic, and (4) updatable across
80+
the entire project (instead of per-release or per-distribution).
81+
82+
.. csv-table::
83+
:header: "Mechanism", "Machine-readable?", "General?", "Index-agnostic?", "Project updatable?"
84+
85+
"Trove classifiers", "✅", "✅", "✅", "❌"
86+
"Yanking", "✅", "❌", "✅", "✅"
87+
"Project statuses", "✅", "✅", "❌", "✅"
88+
89+
This PEP proposes adopting PyPI's project statuses as an index-agnostic
90+
mechanism, satisfying all four conditions.
91+
92+
Specification
93+
=============
94+
95+
This PEP specifies two aspects: a set of project status markers,
96+
as well as their presentation in the standard HTML and JSON indices.
97+
98+
Project status markers
99+
----------------------
100+
101+
This PEP proposes the following project status markers.
102+
103+
A project **MUST** have exactly one project status marker at any given time.
104+
105+
Indices **MAY** implement any subset of the status markers specified in this
106+
PEP, as applicable.
107+
108+
``active``
109+
~~~~~~~~~~
110+
111+
Description: The project is active. This is the default status for a project.
112+
113+
Index semantics: none.
114+
115+
Installer semantics: none.
116+
117+
``archived``
118+
~~~~~~~~~~~~
119+
120+
Description: The project does not expect to be updated in the future.
121+
122+
Index semantics:
123+
124+
* The index hosting the project **MUST NOT** allow uploads of new distributions to
125+
the project.
126+
* The index **MUST** offer existing distributions of the project for download.
127+
128+
Installer semantics:
129+
130+
* Installers **MAY** produce warnings about a project's archival.
131+
132+
``quarantined``
133+
~~~~~~~~~~~~~~~
134+
135+
Description: The project is considered generally unsafe for use, e.g. due to
136+
malware.
137+
138+
Index semantics:
139+
140+
* The index hosting the project **MUST NOT** allow uploads of new distributions to
141+
the project.
142+
* The index **MUST NOT** offer any distributions of the project for download.
143+
144+
Installer semantics:
145+
146+
* Installers **MAY** produce warnings about a project's quarantine, although
147+
doing so is effectively moot (as the index will not offer any distributions
148+
for installation).
149+
150+
``deprecated``
151+
~~~~~~~~~~~~~~
152+
153+
Description: The project is considered obsolete, and may have been superseded
154+
by another project.
155+
156+
Index semantics:
157+
158+
* The index hosting the project **MUST** allow uploads of new distributions to
159+
the project.
160+
* The index **MUST** offer existing distributions of the project for download.
161+
162+
Installer semantics:
163+
164+
* Installers **MAY** produce warnings about a project's deprecation.
165+
166+
Status markers in the index APIs
167+
--------------------------------
168+
169+
This PEP defines version 1.4 of the index APIs.
170+
171+
HTML index
172+
~~~~~~~~~~
173+
174+
The following changes are made to the
175+
:ref:`simple repository API <packaging:simple-repository-api-base>`:
176+
177+
* The index **SHALL** define the ``pypi:repository-version`` as ``1.4``.
178+
* The index **SHOULD** add an appropriate ``pypi:project-status`` meta tag, with
179+
a ``content`` of the project's status marker. The index **MAY** choose to omit
180+
the ``pypi:project-status`` meta tag if the project is marked as ``active``.
181+
182+
For example, the following would be a valid HTML index response for
183+
``sampleproject`` after is has been marked as ``quarantined``:
184+
185+
.. code-block:: html
186+
:emphasize-lines: 5
187+
188+
<!DOCTYPE html>
189+
<html>
190+
<head>
191+
<meta name="pypi:repository-version" content="1.4">
192+
<meta name="pypi:project-status" content="quarantined">
193+
<title>Links for sampleproject</title>
194+
</head>
195+
<body>
196+
<h1>Links for sampleproject</h1>
197+
</body>
198+
</html>
199+
200+
Observe that, per the ``quarantined`` semantics above, the index response
201+
contains no distribution links for the project.
202+
203+
JSON index
204+
~~~~~~~~~~
205+
206+
The following changes are made to the
207+
:ref:`JSON simple index <packaging:simple-repository-api-json>`:
208+
209+
* The index **SHALL** define the ``meta.api-version`` as ``1.4``.
210+
* The index **SHOULD** include a ``project-status`` key in the JSON response,
211+
with a value of the project's status marker. The index **MAY** choose to omit
212+
the ``project-status`` key if the project is marked as ``active``.
213+
214+
For example, the following would be a valid JSON index response for
215+
``sampleproject`` after is has been marked as ``quarantined``:
216+
217+
.. code-block:: json
218+
:emphasize-lines: 5
219+
220+
{
221+
"meta": {
222+
"api-version": "1.4"
223+
},
224+
"project-status": "quarantined",
225+
"alternate-locations": [],
226+
"files": [],
227+
"name": "sampleproject",
228+
"versions": [
229+
"1.2.0",
230+
"1.3.0",
231+
"1.3.1",
232+
"2.0.0",
233+
"3.0.0",
234+
"4.0.0"
235+
]
236+
}
237+
238+
Observe that, like with the HTML index, the JSON response contains no
239+
distribution links for the ``quarantined`` project.
240+
241+
Future Considerations
242+
=====================
243+
244+
This PEP defines only four project status markers: ``active``, ``archived``,
245+
``quarantined``, and ``deprecated``.
246+
247+
Future PEPs (or PyPA standards processes) may define additional project
248+
status markers, as needed.
249+
250+
As specified in this PEP, project status markers are "bare," i.e. they
251+
convey no additional user-controlled metadata (such as an explanation
252+
for a project's archival).
253+
254+
A future PEP may choose to extend the project
255+
status mechanism to include user-controlled metadata, in a manner similar
256+
to the free-form text allowed during release yanking.
257+
258+
Security Implications
259+
=====================
260+
261+
This PEP does not identify any positive or negative security implications
262+
associated with adding project status markers.
263+
264+
How to Teach This
265+
=================
266+
267+
Educating the Python community about this PEP has two aspects:
268+
269+
* Ordinary package maintainers will need to be informed of their ability to
270+
set project status markers, e.g. to inform their downstreams that
271+
a project has been archived or deprecated.
272+
273+
If this PEP is accepted, the authors of this PEP will coordinate with
274+
PyPI on appropriate maintainer-oriented documentation and communication,
275+
including feature announcement blog posts and updates to
276+
`PyPI's user documentation <https://docs.pypi.org>`_.
277+
278+
* Installer and index maintainers will need to be informed of the new project
279+
status markers, and how to interpret them.
280+
281+
If this PEP is accepted, the authors of this PEP will perform its
282+
implementation on PyPI, serving as a reference implementation for other
283+
indices.
284+
285+
This PEP does not *mandate* any changes in installer behavior. However,
286+
if this PEP is accepted, the authors of this PEP will coordinate with
287+
the maintainers of popular installers (e.g. ``pip``) to help each determine
288+
the extent to which they wish to surface project statuses.
289+
290+
Rejected Ideas
291+
==============
292+
293+
Using "reserved" keys
294+
---------------------
295+
296+
One alternative to this PEP is to avoid standardizing project status
297+
markers directly, but instead leverage existing mechanisms within the standards
298+
to communicate them in a non-standard fashion.
299+
300+
For example, the `JSON simple index <packaging:simple-repository-api-json>`_
301+
says the following:
302+
303+
Keys (at any level) with a leading underscore are reserved as private for
304+
index server use. No future standard will assign a meaning to any such key.
305+
306+
In effect, this means that the following would be standards-compliant:
307+
308+
.. code-block:: json
309+
:emphasize-lines: 5
310+
311+
{
312+
"meta": {
313+
"api-version": "1.4"
314+
},
315+
"_project-status": "quarantined",
316+
"alternate-locations": [],
317+
"files": [],
318+
"name": "sampleproject",
319+
"versions": [
320+
"1.2.0",
321+
"1.3.0",
322+
"1.3.1",
323+
"2.0.0",
324+
"3.0.0",
325+
"4.0.0"
326+
]
327+
}
328+
329+
However, this approach has several drawbacks:
330+
331+
* Standards-aligned tools (such as ``pip``, ``pip-audit``, and ``uv``)
332+
may find it unacceptable to use a "reserved" key, since that key will
333+
have no standard semantics or compatibility properties.
334+
* The "reserved" approach is only suitable for the JSON simple index;
335+
no equivalent mechanism exists for the HTML simple index.
336+
This would disadvantage consumers of the HTML simple index, as well as
337+
mirror implementations that may consume the JSON index but only expose
338+
an HTML index.
339+
340+
Project markers in PyPI's non-standard JSON API
341+
-----------------------------------------------
342+
343+
Another standardization-avoidance alternative is to expose project status
344+
markers, but only in PyPI's
345+
`non-standard JSON API <https://docs.pypi.org/api/json/>`_. PyPI has full
346+
control over the layout of this API, and could include a ``project-status``
347+
or similar key without needing a PEP or underscore prefix.
348+
349+
This has similar drawbacks as the "reserved" keys approach above,
350+
and more generally deepens the differences between the standard
351+
and non-standard APIs.
352+
353+
Multiple project status markers at once
354+
---------------------------------------
355+
356+
An earlier version of this PEP considered proposing support for
357+
multiple project markers at once. For example, a project could be marked
358+
as both ``archived`` and ``quarantined``.
359+
360+
After consideration, this was rejected for complexity reasons: having multiple
361+
project status markers requires the PEP to specify a conflict resolution
362+
mechanism when merging their semantics, as well as as state machine for which
363+
markers are exclusive (for example, ``active`` is conceptually exclusive with
364+
all other markers, while ``archived`` and ``quarantined`` are conceptually
365+
compatible with each other).
366+
367+
Copyright
368+
=========
369+
370+
This document is placed in the public domain or under the CC0-1.0-Universal
371+
license, whichever is more permissive.

0 commit comments

Comments
 (0)