Skip to content

Commit 5e9d4b6

Browse files
committed
Respond to PEP Delegate's feedback
* Remove the "action" keys in request bodies, and substitute for explicit end-points. This feels much more REST-ish. This is for both the publishing and file upload sessions. * Add an `authentication` cross-reference anchor. * Add a Recommendations for Client Implementers section for how clients like twine, uv, and GitHub actions *might* utilize the PEP 694 API. * Update the Change Log accordingly.
1 parent 5f1edbb commit 5e9d4b6

File tree

1 file changed

+246
-27
lines changed

1 file changed

+246
-27
lines changed

peps/pep-0694.rst

Lines changed: 246 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ the url structure of a domain. For example, the root endpoint could be
253253
The choice of the root endpoint is left up to the index operator.
254254

255255

256+
.. _authentication:
257+
256258
Authentication for Upload 2.0 API
257259
----------------------------------
258260

@@ -421,6 +423,8 @@ The successful response includes the following content:
421423
"stage": "...",
422424
"upload": "...",
423425
"session": "...",
426+
"publish": "...",
427+
"extend": "...",
424428
},
425429
"mechanisms": ["http-post-bytes"],
426430
"session-token": "<token-string>",
@@ -481,7 +485,9 @@ Multiple Session Creation Requests
481485
If a second attempt to create a session is received for the same name-version pair while a session for that
482486
pair is in the ``pending``, ``processing``, or ``complete`` state, then a new session is *not* created.
483487
Instead, the server **MUST** respond with a ``409 Conflict`` and **MUST** include a ``Location`` header that
484-
points to the :ref:`session status URL <publishing-session-status>`.
488+
points to the :ref:`session status URL <publishing-session-status>`. Subsequent session creation requests
489+
**MUST** be performed with the same credentials as the in-progress session, otherwise a ``403 Forbidden`` is
490+
returned.
485491

486492
For sessions in the ``error`` or ``canceled`` state, a new session is created with same ``201 Created``
487493
response and payload, except that the :ref:`publishing session status URL <publishing-session-status>`,
@@ -495,12 +501,16 @@ Publishing Session Links
495501
For the ``links`` key in the success JSON, the following sub-keys are valid:
496502

497503
``session``
498-
The endpoint where actions for this session can be performed,
499-
including :ref:`publishing this session <publishing-session-completion>`,
500-
:ref:`canceling and discarding the session <publishing-session-cancellation>`,
501-
:ref:`querying the current session status <publishing-session-status>`,
502-
and :ref:`requesting an extension of the session lifetime <publishing-session-extension>`
503-
(*if* the server supports it).
504+
The endpoint where the session resource can be accessed for
505+
:ref:`querying the current session status <publishing-session-status>` (via ``GET``)
506+
and :ref:`canceling and discarding the session <publishing-session-cancellation>` (via ``DELETE``).
507+
508+
``publish``
509+
The endpoint for :ref:`publishing this session <publishing-session-completion>` (via ``POST``).
510+
511+
``extend``
512+
The endpoint for :ref:`requesting an extension of the session lifetime <publishing-session-extension>`
513+
(via ``POST``). If the server does not support session extensions, this key **MUST** be omitted.
504514

505515
``upload``
506516
The endpoint session clients will use to initiate a :ref:`file upload session <file-upload-session>`
@@ -550,7 +560,7 @@ Complete a Publishing Session
550560
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
551561

552562
To complete a session and publish the files that have been included in it, a client issues a
553-
``POST`` request to the ``session`` :ref:`link <publishing-session-links>`
563+
``POST`` request to the ``publish`` :ref:`link <publishing-session-links>`
554564
given in the :ref:`session creation response body <publishing-session-response>`.
555565

556566
The request looks like:
@@ -560,8 +570,7 @@ The request looks like:
560570
{
561571
"meta": {
562572
"api-version": "2.0"
563-
},
564-
"action": "publish",
573+
}
565574
}
566575
567576
@@ -614,7 +623,8 @@ Publishing Session Extension
614623

615624
Servers **MAY** allow clients to extend sessions, but the overall lifetime and number of extensions
616625
allowed is left to the server. To extend a session, a client issues a ``POST`` request to the
617-
:ref:`links.session <publishing-session-links>` URL (same as above, also the ``Location`` header).
626+
:ref:`links.extend <publishing-session-links>` URL. If the server does not support session extensions,
627+
the ``links.extend`` key will not be present in the response.
618628

619629
The request looks like:
620630

@@ -624,7 +634,6 @@ The request looks like:
624634
"meta": {
625635
"api-version": "2.0"
626636
},
627-
"action": "extend",
628637
"extend-for": 3600
629638
}
630639
@@ -756,7 +765,9 @@ The successful response includes the following:
756765
"api-version": "2.0"
757766
},
758767
"links": {
759-
"file-upload-session": "..."
768+
"file-upload-session": "...",
769+
"complete": "...",
770+
"extend": "..."
760771
},
761772
"status": "pending",
762773
"expires-at": "2025-08-01T13:00:00Z",
@@ -801,30 +812,35 @@ File Upload Session Links
801812
For the ``links`` key in the response payload, the following sub-keys are valid:
802813

803814
``file-upload-session``
804-
The endpoint where actions for this file-upload-session can be performed. including :ref:`completing a
805-
file upload session <file-upload-session-completion>`, :ref:`canceling and discarding the file upload
806-
session <file-upload-session-cancellation>`, :ref:`querying the current file upload session status
807-
<file-upload-session-status>`, and :ref:`requesting an extension of the file upload session lifetime
808-
<file-upload-session-extension>` (*if* the server supports it).
815+
The endpoint where the file upload session resource can be accessed for
816+
:ref:`querying the current file upload session status <file-upload-session-status>` (via ``GET``)
817+
and :ref:`canceling and discarding the file upload session <file-upload-session-cancellation>` (via ``DELETE``).
818+
819+
``complete``
820+
The endpoint for :ref:`completing a file upload session <file-upload-session-completion>` (via ``POST``).
821+
822+
``extend``
823+
The endpoint for :ref:`requesting an extension of the file upload session lifetime
824+
<file-upload-session-extension>` (via ``POST``). If the server does not support file upload session
825+
extensions, this key **MUST** be omitted.
809826

810827
.. _file-upload-session-completion:
811828

812829
Complete a File Upload Session
813830
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
814831

815832
To complete a file upload session, which indicates that the file upload mechanism has been executed
816-
and did not produce an error, a client issues a ``POST`` to the ``file-upload-session`` link in the
817-
file upload session creation response body.
833+
and did not produce an error, a client issues a ``POST`` to the ``complete`` :ref:`link
834+
<file-upload-session-links>` in the file upload session creation response body.
818835

819-
The requests looks like:
836+
The request looks like:
820837

821838
.. code-block:: json
822839
823840
{
824841
"meta": {
825842
"api-version": "2.0"
826-
},
827-
"action": "complete",
843+
}
828844
}
829845
830846
If the server is able to immediately complete the file upload session, it may do so and return a ``201
@@ -887,8 +903,9 @@ File Upload Session Extension
887903

888904
Servers **MAY** allow clients to extend file upload sessions, but the overall lifetime and number of
889905
extensions allowed is left to the server. To extend a file upload session, a client issues a ``POST`` request
890-
to the ``links.file-upload-session`` URL from the :ref:`file upload session creation response
891-
<file-upload-session-response>`.
906+
to the ``extend`` :ref:`link <file-upload-session-links>` from the :ref:`file upload session creation response
907+
<file-upload-session-response>`. If the server does not support file upload session extensions,
908+
the ``links.extend`` key will not be present in the response.
892909

893910
The request looks like:
894911

@@ -898,7 +915,6 @@ The request looks like:
898915
"meta": {
899916
"api-version": "2.0"
900917
},
901-
"action": "extend",
902918
"extend-for": 3600
903919
}
904920
@@ -1035,6 +1051,200 @@ If a server intends to precisely match the behavior of another server's implemen
10351051
with that implementation's file upload mechanism name.
10361052

10371053

1054+
.. _client-recommendations:
1055+
1056+
Recommendations for Client Implementers
1057+
=======================================
1058+
1059+
This section is non-normative and provides guidance for client tool authors
1060+
implementing the Upload 2.0 protocol. These recommendations are suggestions
1061+
based on the expected usage patterns of the protocol; client authors are free
1062+
to implement alternative approaches that best suit their users' needs.
1063+
1064+
General Workflow
1065+
----------------
1066+
1067+
A typical upload workflow using the Upload 2.0 protocol follows these steps:
1068+
1069+
1. Create a :ref:`publishing session <publishing-session-create>` for the project name and version.
1070+
2. For each artifact (sdist, wheels), :ref:`create a file upload session <file-upload-session>`,
1071+
execute the negotiated upload mechanism, and :ref:`complete the file upload session
1072+
<file-upload-session-completion>`.
1073+
3. Optionally, if the index supports :ref:`stage previews <staged-preview>`, use the ``links.stage``
1074+
URL to test the release before publishing.
1075+
4. :ref:`Publish the session <publishing-session-completion>` to make the release public,
1076+
or :ref:`cancel it <publishing-session-cancellation>` if issues are discovered.
1077+
1078+
Clients **SHOULD** handle failures gracefully at each step. If an error occurs during file upload,
1079+
the client should :ref:`cancel the file upload session <file-upload-session-cancellation>`. If an
1080+
unrecoverable error occurs at any point, the client should :ref:`cancel the publishing session
1081+
<publishing-session-cancellation>` to clean up server-side resources.
1082+
1083+
Parallel Uploads
1084+
~~~~~~~~~~~~~~~~
1085+
1086+
Clients **MAY** upload multiple files in parallel by creating and executing multiple file upload
1087+
sessions concurrently within the same publishing session. This can significantly improve upload
1088+
times for releases with many wheel variants. However, clients should be prepared for servers that
1089+
do not support parallel uploads and may return ``409 Conflict`` if parallel uploads are attempted.
1090+
1091+
Session Management
1092+
~~~~~~~~~~~~~~~~~~
1093+
1094+
Clients should monitor the ``expires-at`` timestamp in session responses. For long-running uploads
1095+
(e.g., large files on slow connections), clients may need to :ref:`request session extensions
1096+
<publishing-session-extension>` if the ``links.extend`` endpoint is available. If the server does
1097+
not support extensions (indicated by the absence of ``links.extend``), clients should warn users
1098+
when uploads may exceed the session lifetime.
1099+
1100+
Suggested Command-Line Interfaces
1101+
---------------------------------
1102+
1103+
The following examples illustrate how existing tools might expose the Upload 2.0 protocol to users.
1104+
These are suggestions only; actual implementations may vary.
1105+
1106+
twine
1107+
~~~~~
1108+
1109+
`twine <https://twine.readthedocs.io/>`__ currently provides a simple ``twine upload dist/*``
1110+
command. The Upload 2.0 protocol could be exposed through additional options:
1111+
1112+
``twine upload dist/*``
1113+
Maintains backward compatibility. Uses the Upload 2.0 protocol if available, falling back to
1114+
the legacy protocol if not. Creates a session, uploads all files, and publishes immediately.
1115+
1116+
``twine upload --stage dist/*``
1117+
Uses the Upload 2.0 protocol to create a session and upload files, but does not publish.
1118+
This is useful even when the index does not support stage preview URLs, as it still provides
1119+
the atomic release semantics of Upload 2.0. If the index supports stage previews, prints the
1120+
``links.stage`` URL for testing. Prints a session identifier that can be used with subsequent
1121+
commands. This session identifier is local to the client and is mapped internally to the
1122+
in-progress server session.
1123+
1124+
``twine publish <session-id>``
1125+
Publishes a previously staged session.
1126+
1127+
``twine cancel <session-id>``
1128+
Cancels a staged session and discards all uploaded files.
1129+
1130+
``twine status <session-id>``
1131+
Queries and displays the current status of a session.
1132+
1133+
uv
1134+
~~
1135+
1136+
`uv <https://docs.astral.sh/uv/>`__ could provide similar functionality with additional integration:
1137+
1138+
``uv publish dist/*``
1139+
Creates a session, uploads all files, and publishes. May leverage parallel uploads for
1140+
faster publishing of multiple wheels.
1141+
1142+
``uv publish --stage dist/*``
1143+
Uploads without publishing. Like twine, this is valuable even without stage preview support.
1144+
1145+
``uv publish --test-install dist/*``
1146+
If the index supports stage previews, uploads files, installs the package from the stage URL
1147+
into a temporary virtual environment, optionally runs a smoke test command, and only publishes
1148+
if successful. This provides an integrated "upload, test, publish" workflow.
1149+
1150+
GitHub Actions
1151+
~~~~~~~~~~~~~~
1152+
1153+
The `pypa/gh-action-pypi-publish <https://github.com/pypa/gh-action-pypi-publish>`__ action could
1154+
leverage staged releases to enable powerful CI/CD workflows. A multi-job workflow might look like:
1155+
1156+
.. code-block:: yaml
1157+
1158+
jobs:
1159+
upload:
1160+
runs-on: ubuntu-latest
1161+
outputs:
1162+
stage-url: ${{ steps.upload.outputs.stage-url }}
1163+
session-id: ${{ steps.upload.outputs.session-id }}
1164+
steps:
1165+
- uses: actions/download-artifact@v4
1166+
with:
1167+
name: dist
1168+
path: dist/
1169+
- id: upload
1170+
uses: pypa/gh-action-pypi-publish@v2
1171+
with:
1172+
stage-only: true # Upload but don't publish
1173+
1174+
test:
1175+
needs: upload
1176+
runs-on: ubuntu-latest
1177+
steps:
1178+
- uses: actions/setup-python@v5
1179+
- name: Test staged release
1180+
run: |
1181+
pip install --extra-index-url "${{ needs.upload.outputs.stage-url }}" my-package
1182+
python -c "import my_package; my_package.smoke_test()"
1183+
1184+
publish:
1185+
needs: [upload, test]
1186+
runs-on: ubuntu-latest
1187+
steps:
1188+
- uses: pypa/gh-action-pypi-publish@v2
1189+
with:
1190+
publish-session: ${{ needs.upload.outputs.session-id }}
1191+
1192+
This pattern allows the actual PyPI artifacts to be tested in a realistic installation scenario
1193+
before being published. If the test job fails, the workflow can include a cleanup job to cancel
1194+
the session:
1195+
1196+
.. code-block:: yaml
1197+
1198+
cancel-on-failure:
1199+
needs: [upload, test]
1200+
if: failure()
1201+
runs-on: ubuntu-latest
1202+
steps:
1203+
- uses: pypa/gh-action-pypi-publish@v2
1204+
with:
1205+
cancel-session: ${{ needs.upload.outputs.session-id }}
1206+
1207+
Even when the index does not support stage preview URLs, the staged upload pattern is still
1208+
valuable as it ensures atomic releases: either all artifacts are published together, or none are.
1209+
1210+
Error Handling
1211+
--------------
1212+
1213+
Clients should implement robust error handling for the multi-step upload process:
1214+
1215+
**File upload failures**: If a file upload fails (network error, validation error, etc.), the
1216+
client should :ref:`cancel that file upload session <file-upload-session-cancellation>` before
1217+
retrying. The client may then create a new file upload session for the same filename.
1218+
1219+
**Partial upload recovery**: If some files have been successfully uploaded but others fail, the
1220+
client has options:
1221+
1222+
- Cancel the entire publishing session and start over.
1223+
- Cancel only the failed file upload sessions and retry those files.
1224+
- If using ``--stage`` mode, leave the session open for manual intervention.
1225+
1226+
**Session expiration**: If a session expires during upload, the client must create a new publishing
1227+
session and re-upload all files. Clients should monitor ``expires-at`` and warn users proactively.
1228+
1229+
**Publishing failures**: If the publish request fails, the session remains in its current state.
1230+
The client can query the session status to determine the cause and retry the publish operation.
1231+
1232+
**Graceful cancellation**: When a user interrupts an upload (e.g., Ctrl+C), clients should attempt
1233+
to cancel the publishing session to avoid leaving orphaned sessions on the server.
1234+
1235+
Legacy API Fallback
1236+
-------------------
1237+
1238+
During the transition period, clients **SHOULD** support both the Upload 2.0 and legacy protocols.
1239+
A suggested approach:
1240+
1241+
1. Attempt to use Upload 2.0 by checking for the 2.0 endpoint or using content negotiation.
1242+
2. If the server does not support Upload 2.0 (e.g., returns ``404`` or ``406``), fall back to the
1243+
legacy protocol.
1244+
3. Provide a command-line option to force a specific protocol version if needed for debugging or
1245+
compatibility.
1246+
1247+
10381248
FAQ
10391249
===
10401250

@@ -1105,7 +1315,16 @@ as experience is gained operating Upload 2.0.
11051315
Change History
11061316
==============
11071317

1108-
* `06-Dec-2025 <TBD>`__
1318+
* `26-Jan-2026 <TBD>`__
1319+
1320+
* Session actions now use dedicated endpoint links instead of an ``action`` key in request bodies.
1321+
Publishing sessions add ``links.publish`` and ``links.extend``; file upload sessions add
1322+
``links.complete`` and ``links.extend``. The ``links.session`` and ``links.file-upload-session``
1323+
endpoints are now used only for ``GET`` (status) and ``DELETE`` (cancel) operations.
1324+
* Add non-normative :ref:`Recommendations for Client Implementers <client-recommendations>` section
1325+
with suggested UX patterns for tools like twine, uv, and GitHub Actions.
1326+
1327+
* `06-Dec-2025 <https://discuss.python.org/t/pep-694-pypi-upload-api-2-0-round-2/101483/35>`__
11091328

11101329
* Error responses conform to the :rfc:`9457` format.
11111330

0 commit comments

Comments
 (0)