Skip to content

Commit 52b9fd0

Browse files
woodruffwmikethemandi
authored
docs: add a PEP 792 blog post (#18537)
* docs: add a PEP 792 blog post Signed-off-by: William Woodruff <[email protected]> * Update docs/blog/posts/2025-08-14-project-status-markers.md Co-authored-by: Mike Fiedler <[email protected]> * Apply suggestions from code review Co-authored-by: Dustin Ingram <[email protected]> * tweak self-description Signed-off-by: William Woodruff <[email protected]> * minimize use of 'distribution' Signed-off-by: William Woodruff <[email protected]> * add HTML example Signed-off-by: William Woodruff <[email protected]> * address more feedback Signed-off-by: William Woodruff <[email protected]> * feedback Signed-off-by: William Woodruff <[email protected]> * remove starjacking Signed-off-by: William Woodruff <[email protected]> --------- Signed-off-by: William Woodruff <[email protected]> Co-authored-by: Mike Fiedler <[email protected]> Co-authored-by: Dustin Ingram <[email protected]>
1 parent 58dc95e commit 52b9fd0

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed

docs/blog/.authors.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,7 @@ authors:
3131
name: Facundo Tuesca
3232
description: Senior Engineer, Trail of Bits (Guest Author)
3333
avatar: https://github.com/facutuesca.png
34+
woodruffw:
35+
name: William Woodruff
36+
description: PyPI Contributor
37+
avatar: https://github.com/woodruffw.png
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
---
2+
title: PyPI now serves project status markers in API responses
3+
description: >
4+
PyPI has implemented PEP 792, and is now serving
5+
project status markers in its standard HTML and JSON APIs.
6+
authors:
7+
- woodruffw
8+
date: 2025-08-14
9+
tags:
10+
- security
11+
- integrations
12+
---
13+
14+
PyPI now serves [project status markers] in its standard
15+
[index APIs]. This allows downstream consumers (like Python package installers and
16+
index mirrors) to retrieve project statuses programmatically and use them to
17+
inform users when a project is archived or quarantined.
18+
19+
## Summary
20+
21+
* PyPI has implemented project status markers as proposed and accepted in
22+
[PEP 792].
23+
* As of today, PyPI supports three standard statuses: **active** (the default),
24+
**archived**, and **quarantined**.
25+
* Downstream consumers can now retrieve these statuses via the standard index APIs
26+
and use them to inform users about the state of a project.
27+
28+
See the [project archival] and [project quarantine] announcement posts
29+
for additional information on PyPI's implementation of those individual statuses.
30+
31+
<!-- more -->
32+
33+
## Background
34+
35+
Many Python regularly find themselves asking the same
36+
questions again and again when evaluating a new dependency:
37+
38+
* Is the dependency deprecated, potentially in favor of another project?
39+
* If a vulnerability is discovered in the dependency, is it likely to be
40+
patched?
41+
* Can I expect major future changes to the dependency, or is it
42+
"done" (i.e. feature complete)?
43+
44+
These questions (and many others in the domain of supply chain security)
45+
essentially boil down to a single question:
46+
**what is the status of this project?**
47+
48+
## The status quo before status markers
49+
50+
Before [PEP 792], Python packaging had no less than three overlapping
51+
solutions for determining a project's status:
52+
53+
* Individual releases of a project could include a `Development Status`
54+
[trove classifier] in their metadata, such as
55+
`Development Status :: 7 - Inactive` to indicate that the project is no
56+
longer actively maintained.
57+
58+
However, trove classifiers occur at the _distribution_ level, leading
59+
to two problems:
60+
61+
1. To update a project's status, the project's maintainer must upload
62+
a new release with the updated classifier. This is unnecessarily
63+
onerous, particularly when the intent is to _stop_ updating the
64+
project!
65+
2. Classifiers do not apply retroactively, meaning that all _previous_
66+
releases of a project continue to have their original
67+
classifiers. This results in a misleading view of the project's status:
68+
a downstream that pins to `sampleproject==1.2.3` may fail to realize
69+
that `sampleproject===1.2.4` signals that the entire project is now
70+
inactive.
71+
72+
* Indices can mark individual files (or entire releases) as "yanked," per the
73+
[file yanking specification]. Yanked files are effectively
74+
soft-deleted, meaning that they'll be skipped by complying installers
75+
during resolution but not if explicitly pinned by the user.
76+
77+
Yanking is a useful tool for mitigating accidental vulnerabilities
78+
or compatibility breakages in a release, but it has the same "scope"
79+
issue as classifiers: it applies at the file and release level,
80+
not at the project level.
81+
82+
Moreover, the semantics of yanking aren't appropriate for all potential
83+
statuses: soft deletion is still disruptive, whereas statuses like
84+
"archived" and "deprecated" suggest that the project is still
85+
suitable for installation, so long as the user can be made aware of
86+
its status.
87+
88+
* PyPI itself has "project statuses," which apply to the entire project.
89+
These statuses were not standardized, and therefore only appeared
90+
on user-facing HTML pages, not in the standard APIs. This made
91+
them difficult to retrieve programmatically, limiting their usefulness.
92+
93+
Beyond these partial solutions many downstreams also apply heuristics
94+
to determine a project's status, such as checking for recent project
95+
(or source repository) activity or using popularity metrics like
96+
GitHub stars as a proxy for project health. However, these heuristics
97+
can be manipulated or outright incorrect, such as when a project is feature
98+
complete and therefore has no recent activity.
99+
100+
Overall, these partial solutions and heuristics point to a need for
101+
something better.
102+
103+
## Project status markers
104+
105+
That brings us to the new feature: project status markers.
106+
107+
Project status markers are a Python packaging standard derived from PyPI's
108+
existing project statuses. The standard defines four project statuses,
109+
which have both index-side and installer-side semantics:
110+
111+
* **active**: Indicates that the project is active. This is the default
112+
status, meaning that any project that does not explicitly
113+
declare a status is considered active. Active projects are
114+
not subject to any restrictions on upload or installation.
115+
* **archived**: Indicates that the project does not expect to be updated
116+
in the future. When a project is **archived**, PyPI will not allow
117+
new uploads to the project, and installers are encouraged to
118+
inform users about the project's archival.
119+
* **quarantined**: Indicates that the project is considered generally
120+
unsafe for use, e.g. due to malware. When a project is **quarantined**, PyPI
121+
will not offer it for installation, and installers are encouraged to
122+
produce a warning when users attempt to install it[^warning].
123+
* **deprecated**: Indicates that the project is considered obsolete,
124+
and may have been superceded by another project. Unlike archived projects,
125+
deprecated projects can still be uploaded to, but installers are encouraged
126+
to inform users about the project's deprecation.
127+
128+
Of these statuses, PyPI currently supports **active**, **archived**, and
129+
**quarantined**. PyPI doesn't support **deprecated** yet, but we'll be looking
130+
at supporting it now that the MVP is complete.
131+
132+
Beyond the statuses themselves, the standard also defines an optional
133+
"status reason" that can be used to provide additional context about the status.
134+
PyPI doesn't currently expose status reasons, but may do so in the future.
135+
136+
## Consuming status markers
137+
138+
The standard is one thing, but let's see how to actually get status
139+
markers from PyPI's [index APIs].
140+
141+
Status markers are available in both the HTML and JSON index APIs.
142+
For the HTML API the `<meta>` fields are:
143+
144+
* `pypi:project-status` for the project status itself (or **active** by default)
145+
* `pypi:project-status-reason` for the project status reason (if present)
146+
147+
For example:
148+
149+
```sh
150+
curl --silent \
151+
-H "Accept: application/vnd.pypi.simple.v1+html" \
152+
https://pypi.org/simple/pepy/ \
153+
| htmlq --pretty 'head meta[name="pypi:project-status"]'
154+
```
155+
156+
Yields:
157+
158+
```html
159+
<meta name="pypi:project-status" content="archived">
160+
```
161+
162+
Within the JSON API, the project status is available via the
163+
top-level `project-status` object, which contains `status` and `reason`
164+
fields corresponding to the HTML API fields above.
165+
166+
For example:
167+
168+
```sh
169+
curl --silent \
170+
-H "Accept: application/vnd.pypi.simple.v1+json" \
171+
https://pypi.org/simple/pepy/ \
172+
| jq '."project-status"'
173+
```
174+
175+
Yields:
176+
177+
```json
178+
{
179+
"status": "archived",
180+
}
181+
```
182+
183+
## Conclusion
184+
185+
Starting today, Python package installers and other index consumers can
186+
retrieve status markers from PyPI's standard index APIs.
187+
188+
Our hope is that downstreams *will* consume these markers, and use them
189+
as suggested by the standard. In particular we hope that installers like
190+
[pip] and [uv] will signal relevant statuses to users, helping them
191+
form a better picture of the status of their dependencies as well as set
192+
policies controlling which statuses are acceptable for installation.
193+
194+
## Acknowledgements
195+
196+
[PEP 792] was authored by [William Woodruff] (Astral) and
197+
[Facundo Tuesca] (Trail of Bits). We'd like to thank [Donald Stufft] for being
198+
the PEP's sponsor and PEP delegate. Additionally, we'd like to thank
199+
[Dustin Ingram] and [Mike Fiedler] for their review and feedback on the PEP
200+
and the associated changes to PyPI.
201+
202+
The funding for this feature’s development comes in part from
203+
[Alpha-Omega]. Alpha-Omega’s mission is to protect society by catalyzing
204+
sustainable security improvements to the most critical open-source software
205+
projects and ecosystems.
206+
207+
[project status markers]: https://packaging.python.org/en/latest/specifications/project-status-markers/
208+
[index APIs]: https://docs.pypi.org/api/index-api/
209+
[PEP 792]: https://peps.python.org/pep-0792/
210+
[project archival]: 2025-01-30-archival.md
211+
[project quarantine]: 2024-12-30-quarantine.md
212+
[trove classifier]: https://pypi.org/classifiers
213+
[file yanking specification]: https://packaging.python.org/en/latest/specifications/file-yanking/
214+
[Alpha-Omega]: https://alpha-omega.dev/
215+
[Facundo Tuesca]: https://github.com/facutuesca
216+
[William Woodruff]: https://github.com/woodruffw
217+
[Donald Stufft]: https://github.com/dstufft
218+
[Dustin Ingram]: https://github.com/di
219+
[Mike Fiedler]: https://github.com/miketheman
220+
[pip]: https://pip.pypa.io/
221+
[uv]: https://docs.astral.sh/uv/
222+
223+
[^warning]: This warning is technically moot, as PyPI itself will not offer
224+
any files from quarantined projects for installation.
225+
However, the warning can still help users understand _why_
226+
their installation has failed, and is therefore recommended
227+
by the standard.

0 commit comments

Comments
 (0)