Publishing with a Trusted Publisher
Once you have a Trusted Publisher configured on PyPI (whether "pending" or"normal"), you can publish through it on the associated platform. The tabsbelow describe the setup process for each supported Trusted Publisher.
The easy way
You can use the PyPA'spypi-publishaction to publish your packages.
This looksalmost exactly the same as normal, except that you don'tneed any explicit usernames, passwords, or API tokens: GitHub's OIDC identity providerwill take care of everything for you:
jobs:pypi-publish:name:upload release to PyPIruns-on:ubuntu-latest# Specifying a GitHub environment is optional, but strongly encouragedenvironment:pypipermissions:# IMPORTANT: this permission is mandatory for Trusted Publishingid-token:writesteps:# retrieve your distributions here-name:Publish package distributions to PyPIuses:pypa/gh-action-pypi-publish@release/v1If you're moving away from a password or API token-based authenticationflow, your diff might look like this:
jobs: pypi-publish: name: upload release to PyPI runs-on: ubuntu-latest+ # Specifying a GitHub environment is optional, but strongly encouraged+ environment: pypi+ permissions:+ # IMPORTANT: this permission is mandatory for Trusted Publishing+ id-token: write steps: # retrieve your distributions here - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1- with:- username: __token__- password: ${{ secrets.PYPI_TOKEN }}Note theid-token: write permission: youmust provide this permissionat either the job level (strongly recommended) or workflow level(discouraged). Without it, the publishing actionwon't have sufficient permissions to identify itself to PyPI.
Note
Using the permission at the job level isstrongly encouraged, asit reduces unnecessary credential exposure.
Publishing to indices other than PyPI
The PyPA'spypi-publishaction also supports Trusted Publishing with other (non-PyPI) indices, providedthey have Trusted Publishing enabled (and you've configured your TrustedPublisher on them). For example, here's how you can use Trusted Publishing onTestPyPI:-name:Publish package distributions to TestPyPIuses:pypa/gh-action-pypi-publish@release/v1with:repository-url:https://test.pypi.org/legacy/The manual way
Warning
STOP! You probably don't need this section; it exists only to provide someinternal details about how GitHub Actions and PyPI coordinate using OIDC.If you're an ordinary user, it is strongly recommended that you use the PyPA'spypi-publishaction instead.
Warning
Many of the details described below are implementation-specific,and are not subject to either a standardization process orcompatibility guarantees. They are not part of a public interface,and may be changed at any time. For a stable public interface,youmust use thepypi-publish action.
The process for using an OIDC publisher is:
- Retrieve anOIDC token from the OIDCidentity provider;
- Submit that token to PyPI, which will return a short-lived API key;
- Use that API key as you normally would (e.g. with
twine)
All code below assumes that it's being run in a GitHub Actionsworkflow runner withid-token: write permissions. That permission iscritical; without it, GitHub Actions will refuse to give you an OIDC token.
First, let's grab the OIDC token from GitHub Actions:
resp=$(curl-H"Authorization: bearer$ACTIONS_ID_TOKEN_REQUEST_TOKEN"\"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")Note
Usingaudience=pypi is only correct for PyPI. For TestPyPI, the correctaudience istestpypi. More generally, you can access any instance's expectedOIDC audience via the{index}/_/oidc/audience endpoint:
$curlhttps://pypi.org/_/oidc/audience{"audience":"pypi"}The response to this will be a JSON blob, which contains the OIDC token.We can pull it out usingjq:
oidc_token=$(jq'.value'<<<"${resp}")Finally, we can submit that token to PyPI and get a short-lived API tokenback:
resp=$(curl-XPOSThttps://pypi.org/_/oidc/mint-token-d"{\"token\": \"${oidc_token}\"}")api_token=$(jq-r'.token'<<<"${resp}")# tell GitHub Actions to mask the token in any console logs,# to avoid leaking itecho"::add-mask::${api_token}"This API token can be fed intotwine or any other uploading client:
TWINE_USERNAME=__token__TWINE_PASSWORD=${api_token}twineuploaddist/*This can all be tied together into a single GitHub Actions workflow:
on:release:types:-publishedname:releasejobs:pypi:name:upload release to PyPIruns-on:ubuntu-latestpermissions:id-token:writesteps:-uses:actions/checkout@v3-uses:actions/setup-python@v4with:python-version:"3.x"-name:depsrun:python -m pip install -U build-name:buildrun:python -m build-name:mint API tokenid:mint-tokenrun:|# retrieve the ambient OIDC tokenresp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")oidc_token=$(jq -r '.value' <<< "${resp}")# exchange the OIDC token for an API tokenresp=$(curl -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")api_token=$(jq -r '.token' <<< "${resp}")# mask the newly minted API token, so that we don't accidentally leak itecho "::add-mask::${api_token}"# see the next step in the workflow for an example of using this step outputecho "api-token=${api_token}" >> "${GITHUB_OUTPUT}"-name:publish# gh-action-pypi-publish uses TWINE_PASSWORD automaticallyuses:pypa/gh-action-pypi-publish@release/v1with:password:${{ steps.mint-token.outputs.api-token }}You can use thehttps://pypi.org/project/id/ tool to automatically detectand produce OIDC credentials on Google Cloud services.
First, ensure thatid andtwine are installed in the environment youplan to publish from:
python -m pip install -U id twineIf you're unsure what the email address is for the service account yourservice is using, you can verify it with:
python -m id pypi -d | jq 'select(.email) | .email'Generate an OIDC token from within the environment and store it. Theaudience should be eitherpypi ortestpypi depending on which index you arepublishing to:
oidc_token=$(python -m id pypi)Note
pypi is only correct for PyPI. For TestPyPI, the correctaudience istestpypi. More generally, you can access any instance's expectedOIDC audience via the{index}/_/oidc/audience endpoint:
$curlhttps://pypi.org/_/oidc/audience{"audience":"pypi"}Finally, we can submit that token to PyPI and get a short-lived API tokenback:
resp=$(curl-XPOSThttps://pypi.org/_/oidc/mint-token-d"{\"token\": \"${oidc_token}\"}")api_token=$(jq-r'.token'<<<"${resp}")Note
This is the URL for PyPI. For TestPyPI, the correctdomain should be istest.pypi.org.
This API token can be fed intotwine or any other uploading client:
TWINE_USERNAME=__token__TWINE_PASSWORD=${api_token}twineuploaddist/*ActiveState's Platform works as a zero-config CI solution for yourdependencies to automatically build cross-platform wheels of your PyPIprojects. Once you're set up on the Platform and have linked your PyPI project,you're ready to publish. For more information on getting started withActiveState, gohere. Tobegin:
Publish your package to ActiveState's catalog. This will allowActiveState's Platform to build it for you.
- Run the following command using the State Tool CLI:Replace the placeholder values in the block above with your ActiveState organization name--this will usually be
state publish \ --namespace private/ORGNAME \ --name PKG_NAME PKG_FILENAME \ --depend "builder/python-module-builder@>=0" \ --depend "language/python@>=3" \ --depend "language/python/setuptools@>=43.0.0" \ --depend "language/python/wheel@>=0"USERNAME-org(ORGNAME), package name (PKG_NAME), and the filename of your sdist or source tarball (PKG_FILENAME) and run the command. Take note of the TIMESTAMP in the output.
Note
The namespace must start withprivate/ followed by yourorganization name. You can also append additional 'folder' names if desired.
- After publishing your package to ActiveState, you'll need to create a build script file (
buildscript.as) to build it into a wheel and publish it to PyPI. An example script is shown below. Create a new build script file in the same folder as youractivestate.yamlfile and name itbuildscript.as. Paste the code below, substituting the placeholder values with those from your project: the timestamp of the package you just published (PUBLISHED_TIMESTAMP), the name of the namespace (ie. folder where you published the ingredient, which will look something likeprivate/USERNAME-org) (NAMESPACE), the name of your package (PKG_NAME) and the version (VERSION) you're publishing. Save the changes to the file.at_time="PUBLISHED_TIMESTAMP"publish_receipt=pypi_publisher(attempt=1,audience="testpypi",pypi_uri="test.pypi.org",src=wheels)runtime=state_tool_artifacts(build_flags=[],src=sources)sources=solve(at_time=at_time,platforms=["7c998ec2-7491-4e75-be4d-8885800ef5f2"],requirements=[Req(namespace="language",name="python",version=Eq(value="3.10.13")),Req(namespace="NAMESPACE",name="PKG_NAME",version=Eq(value="VERSION"))],solver_version=null)wheel_srcs=select_ingredient(namespace="NAMESPACE",name="PKG_NAME",src=sources)wheels=make_wheel(at_time=at_time,python_version="3.10.13",src=wheel_srcs)main=runtime - Then, "commit" this build script to the system by running
state commitin your terminal. Now you're ready to publish to PyPI! - To publish your wheel to PyPI, run:
state eval publish_receipt.That's it!
You have successfully published a Python wheel using the ActiveState Platform.
Note
Buildscript tips:
You can leavepypi_uri andaudience fields blank to publishdirectly to the main PyPI repository.
If you experience a network timeout or another transient error, you canincrement theattempt parameter to retry.
The strings afterplatforms = [ are the UUIDs of the supportedplatforms you want to build a wheel for. A list of all supported platforms canbe foundhere.Select all applicable to your project from the list provided.
Note
If you want to test your wheel before publishing it, you follow thesesteps before runningstate eval publish_receipt:1. To build your wheel on its own, runstate eval wheels2. After building your wheel, runstate builds --all to view all of the builds available. Take note of theHASH_ID of your new wheel.3. Runstate builds dl <HASH_ID> to download and test the wheel you've built.
This is an example GitLab workflow that builds and publishes a package to PyPIusing Trusted Publishing. The key differences with a normal workflow are in thedeployment step (publish-job):
- The keyword
id_tokensis used to request an OIDC token from GitLab with namePYPI_ID_TOKENand audiencepypi. - Twine is called to upload the package with no token specified. It sends the OIDC token to PyPI in exchange for a PyPI API token, which is then used to publish the package using
twine.
build-job:stage:buildimage:python:3-bookwormscript:-python -m pip install -U build-cd python_pkg && python -m buildartifacts:paths:-"python_pkg/dist/"publish-job:stage:deployimage:python:3-bookwormdependencies:-build-jobid_tokens:PYPI_ID_TOKEN:# Use "testpypi" if uploading to TestPyPIaud:pypiscript:# Install dependencies-python -m pip install -U twine# Upload to PyPI, add "--repository testpypi" if uploading to TestPyPI# With no token specified, twine will use Trusted Publishing-twine upload python_pkg/dist/*