Skip to content

Latest commit

 

History

History
432 lines (332 loc) · 13.7 KB

File metadata and controls

432 lines (332 loc) · 13.7 KB

Publish package

Finally, you can deploy the package on the :term:`Python Package Index` (:term:`PyPI`) or another index, for example :doc:`gitlab` or :term:`devpi`.

For the :term:`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 :file:`~/.config/pip/pip.conf` file:

[distutils]
index-servers=
    test

[test]
repository = https://test.pypi.org/legacy/
username = veit
.. seealso::
    If you’d like to automate PyPI registration, read `Careful With That PyPI
    <https://glyph.twistedmatrix.com/2017/10/careful-with-that-pypi.html>`_.

After you are registered, you can upload your :term:`Distribution Package` with uv publish.

You can either use uv publish with the option --username __token__ or set the environment variable UV_PUBLISH_USERNAME=__token__ to upload all archives under :file:`/dist` to the :term:`Python Package Index`:

$ uv publish --publish-url https://test.pypi.org/legacy/ --username __token__ dist/*
--publish-url
The URL of the upload endpoint (not the index URL).
--username
The user name for the upload.

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 :file:`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 :func:`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 :term:`Python Package Index` (:term:`PyPI`), you can do so by creating an account on :term:`pypi.org`.

README

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

PyPI

Now register on the :term:`Python Package Index` (:term:`PyPI`) and make sure that two-factor authentication is activated by adding the following to the :file:`~/.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.

.. seealso::
   * `PyPI now supports uploading via API token
     <https://pyfound.blogspot.com/2019/07/pypi-now-supports-uploading-via-api.html>`_
   * `What is two factor authentication and how does it work on PyPI?
     <https://pypi.org/help/#twofa>`_

Finally, you can publish your package on PyPI:

$ uv publish 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 ===.

.. seealso::
   * `PyPI Release Checklist
     <https://cookiecutter-namespace-template.readthedocs.io/en/latest/pypi-release-checklist.html>`_

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 :file:`.github/workflows/pypi.yml` file could look like this:

name: Publish Python Package

 on:
   release:
     types: [created]

jobs:
  test:
    
  package-and-deploy:
    runs-on: ubuntu-latest
    needs: [test]
    steps:
    - name: Checkout
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        fetch-depth: 0
    - name: Set up Python
      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version-file: .python-version
        cache-dependency-path: '**/pyproject.toml'
    - name: Setup cached uv
      uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 # v2.5.0
    - name: Create venv
      run: |
        uv venv
        echo "$PWD/.venv/bin" >> $GITHUB_PATH
    - name: Build
      run: |
        uv build
    - name: Retrieve and publish
      steps:
      - name: Retrieve release distributions
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
        with:
          username: __token__
          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 :samp:`{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 :term:`PyPI`.
.. seealso::

   * `GitHub Actions <https://docs.github.com/en/actions>`_
   * :doc:`cibuildwheel`

Securing the release workflow

Continuous deployment systems used to publish Python packages are a popular target for attacks. You can avoid many of these risks by following a few security recommendations:

Avoid insecure triggers
Workflows that can be triggered by an attacker, particularly those that rely on inputs controlled by the attacker (such as :ref:`pull request <merge-pull-requests>` or :doc:`branch <Python4DataScience:productive/git/branch>` titles), have been used in the past to inject commands. In particular, the pull_request_target trigger in :ref:`github-actions` should be avoided.
Sanitise parameters and inputs
Any workflow parameter or input that can be expanded into an executable command has the potential to be exploited in attacks. Sanitise values by passing them to commands as environment variables to prevent :abbr:`SSTI (Server Side Template Injection)` attacks.
Avoid mutable references

Fix your dependencies in workflows.

Use verifiable deployments
With :ref:`trusted_publishers`, you can use verifiable GitHub environments to build your Python packages. If you use GitHub Actions for continuous delivery, you should use :ref:`zizmorcore` to detect and fix insecure workflows.

Trusted Publishers

Trusted Publishers is a procedure for publishing packages on the :term:`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 Publisher 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 :term:`PyPI`, we must now also create it. This can be done in :menuselection:`Settings --> Environments` for the repository. The name of our environment is release.

  3. Configure the workflow

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

        package-and-deploy:
          runs-on: ubuntu-latest
      +   environment: release
      +   permissions:
      +     id-token: write
          needs: [test]
          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 40–44

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

    - name: Publish package distributions to PyPI
      uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
      with:
        username: __token__
        password: ${{ secrets.PYPI_TOKEN }}

Since 14 November 2024, :term:`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 :ref:`Trusted Publishing <trusted_publishers>` and the GitHub action pypa/gh-action-pypi-publish are used for publishing:

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@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0

Note

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

.. seealso::
   `PyPI now supports digital attestations
   <https://blog.pypi.org/posts/2024-11-14-pypi-now-supports-digital-attestations/>`_

zizmor

zizmor can detect and resolve many security issues in typical GitHub Actions CI/CD configurations. zizmor is designed to integrate with GitHub Actions. A typical GitHub Action we use for zizmor looks like this:

# https://github.com/woodruffw/zizmor
name: Zizmor

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["**"]

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

permissions: {}

jobs:
  zizmor:
    name: Run zizmor
    runs-on: ubuntu-latest
    permissions:
      security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: Run zizmor
        uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
        with:
          persona: pedantic

2FA for all development accounts

You should use two-factor authentication for all your accounts related to development – not just for :term:`PyPI`. Remember your version control accounts (GitHub, GitLab, Codeberg, Forgejo) and email.