Publish package

Finally, you can deploy the package on the Python Package Index (PyPI) or another index, for example GitLab Package Registry or devpi.

For the Python Package Index, you must register with Test PyPI . Test PyPI is a separate instance that is intended for testing and experimenting. To set up an account there, go to https://test.pypi.org/account/register/. Further information can be found at Using TestPyPI.

Now you can create the ~/.pypirc file:

[distutils]
index-servers=
    test

[test]
repository = https://test.pypi.org/legacy/
username = veit

See also

If you’d like to automate PyPI registration, read Careful With That PyPI.

After you are registered, you can upload your Distribution Package with twine. To do this, however, you must first install twine with:

$ uv add --upgrade twine

Tip

Run this command before each release to ensure that all release tools are up to date.

After installing twine you can upload all archives under /dist to the Python Package Index with:

Now you can create your Distribution Packages with:

$ cd /path/to/your/distribution_package
$ rm -rf build dist
$ python -m build

After installing Twine you can upload all archives in /dist to the Python Package Index with:

$ uv run twine upload -r test -s dist/*
-r, --repository

The repository to upload the package.

In our case, the test section from the ~/.pypirc file is used.

-s, --sign

signs the files to be uploaded with GPG.

You will be asked for the password of your signature key that you used to register with Test PyPI . You should then see a similar output:

Uploading distributions to https://test.pypi.org/legacy/
Enter your username: veit
Enter your password:
Uploading example-0.0.1-py3-none-any.whl
100%|█████████████████████| 4.65k/4.65k [00:01<00:00, 2.88kB/s]
Uploading example-0.0.1.tar.gz
100%|█████████████████████| 4.25k/4.25k [00:01<00:00, 3.05kB/s]

Note

If you get an error message similar to

The user 'veit' isn't allowed to upload to project 'example'

you have to choose a unique name for your package:

  1. change the name argument in the setup.py file

  2. remove the dist directory

  3. regenerate the archives

Check

Installation

You can use uv to install your package from Test PyPI and check if it works:

$ $ uv add -i https://test.pypi.org/simple/ mypack

Note

If you have used a different package name, replace it with your package name in the command above.

uv add should install the package from Test PyPI and the output should look something like this:

Resolved 8 packages in 5ms
Installed 7 packages in 36ms
 + mypack==0.1.0

You can test whether your package has been installed correctly by calling main():

$ uv run mypack
Hello from mypack!

Note

The packages on Test-PyPI are only stored temporarily. If you want to upload a package to the real Python Package Index (PyPI), you can do so by creating an account on pypi.org and following the same instructions, but using twine upload dist/*.

README

Also check whether the README.rst is displayed correctly on the test PyPI page.

PyPI

Now register on the Python Package Index (PyPI) and make sure that two-factor authentication is activated by adding the following to the ~/.pypirc file:

[distutils]
index-servers=
    pypi
    test

[test]
repository = https://test.pypi.org/legacy/
username = veit

[pypi]
username = __token__

With this configuration, the name/password combination is no longer used for uploading but an upload token.

Finally, you can publish your package on PyPI:

$ uv run twine upload -r pypi -s dist/*

Note

You cannot simply replace releases as you cannot re-upload packages with the same version number.

Note

Do not remove old versions from the Python Package Index.This only causes work for those who want to keep using that version and then have to switch to old versions on GitHub. PyPI has a yank function that you can use instead. This will ignore a particular version if it is not explicitly specified with == or ===.

GitHub Action

You can also create a GitHub action, which creates a package and uploads it to PyPI at every time a release is created. Such a .github/workflows/pypi.yml file could look like this:

.github/workflows/pypi.yml
 1name: Publish Python Package
 2
 3 on:
 4   release:
 5     types: [created]
 6
 7jobs:
 8  test:
 9    
10  package-and-deploy:
11    runs-on: ubuntu-latest
12    needs: [test]
13    steps:
14    - name: Checkout
15      uses: actions/checkout@v4
16      with:
17        fetch-depth: 0
18    - name: Set up Python
19      uses: actions/setup-python@v5
20      with:
21        python-version-file: .python-version
22        cache-dependency-path: '**/pyproject.toml'
23    - name: Setup cached uv
24      uses: hynek/setup-cached-uv@v2
25    - name: Create venv and install twine
26      run: |
27        uv venv
28        echo "$PWD/.venv/bin" >> $GITHUB_PATH
29        uv add --upgrade twine
30    - name: Build
31      run: |
32        uv build
33    - name: Retrieve and publish
34      steps:
35      - name: Retrieve release distributions
36        uses: actions/download-artifact@v4
37      - name: Publish package distributions to PyPI
38        uses: pypa/gh-action-pypi-publish@release/v1
39        with:
40          username: __token__
41          password: ${{ secrets.PYPI_TOKEN }}
Lines 3–5

This ensures that the workflow is executed every time a new GitHub release is created for the repository.

Line 12

The job waits for the test job to pass before it is executed.

Line 31

Here mypack should be replaced by your package name.

Line 36

The GitHub action actions/download-artifact provides the built distribution packages.

Lines 38–41

The GitHub action pypa/gh-action-pypi-publish publishes the packages with the upload token on PyPI.

See also

Trusted Publishers

Trusted Publishers is a procedure for publishing packages on the PyPI. It is based on OpenID Connect and requires neither a password nor a token. Only the following steps are required:

  1. Add a Trusted Publishers on PyPI

    Depending on whether you want to publish a new package or update an existing one, the process is slightly different:

  2. Create an environment for the GitHub actions

    If we have specified an environment on PyPI, we must now also create it. This can be done in Settings ‣ Environments for the repository. The name of our environment is release.

  3. Configure the workflow

    To do this, we now create the .github/workflows/publish.yml file in our repository:

    .github/workflows/pypi.yml
    10    package-and-deploy:
    11      runs-on: ubuntu-latest
    12  +   environment: release
    13  +   permissions:
    14  +     id-token: write
    15      needs: [test]
    16      steps:
    
    Line 12

    The specification of a GitHub environment is optional, but strongly recommended.

    Lines 13–14

    The write authorisation is required for Trusted Publishing.

    Zeilen 42–44

    username and password are no longer required for the GitHub action pypa/gh-action-pypi-publish.

    40   - name: Publish package distributions to PyPI
    41     uses: pypa/gh-action-pypi-publish@release/v1
    42-    with:
    43-      username: __token__
    44-      password: ${{ secrets.PYPI_TOKEN }}
    

Digital Attestations

Since 14 November 2024, PyPI also supports PEP 740 with Digital Attestations. PyPI uses the in-toto Attestation Framework to generate the Digital Attestations SLSA Provenance and PyPI Publish Attestation (v1).

The creation and publication takes place by default, provided that Trusted Publishing and the GitHub action pypa/gh-action-pypi-publish are used for publishing:

.github/workflows/pypi.yml
jobs:
  pypi-publish:
    name: Upload release to PyPI
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/{YOUR-PYPI-PROJECT-NAME}
    permissions:
      id-token: write
    steps:
    - name: Publish package distributions to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1

Note

Support for the automatic creation of digital attestations and publishing from other Trusted Publisher environments is planned.