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