How to publish a Python package with GitHub Actions using Poetry

Poetry
Poetry
Pyton Package Index
Pyton Package Index

Introduction

Some things have changed since I wrote "Create and Publish a Python Package with Poetry".

For one, PyPI set up trusted publishing, which allows you to publish your Python package directly from GitHub without having to use a password or API token.

Not only is trusted publishing great for security, it's also easier than keeping track of API tokens or passwords.

Let's walk through how you can use GitHub to publish your Python package (with Poetry).

Set up PyPI publishing using GitHub

In your package's Publishing settings (https://pypi.org/manage/project/{{package}}/settings/publishing/) you'll find the trusted publisher form:

PyPI package publishing settings trusted publisher form
PyPI package publishing settings trusted publisher form

The settings I used to publish Blurry, my little static site generator, are:

When the settings are set up in PyPI, the trusted publishing settings will look like this:

PyPI trusted publisher management settings
PyPI trusted publisher management settings

Publish to PyPI from a GitHub action

Now that PyPI knows to expect packages published from GitHub, we can set up the GitHub action to publish a Python package.

Here's the full workflow file (publish.yml):

name: Release

on:
  push:
    tags:
      - '*.*.*'

permissions:
  contents: read

jobs:
  pypi-publish:
    name: Upload release to PyPI
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/project/blurry-cli/
    permissions:
      id-token: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python 3.10
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Install Poetry
        run: |
          curl -sSL https://install.python-poetry.org | python - -y

      - name: Update PATH
        run: echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Update Poetry configuration
        run: poetry config virtualenvs.create false

      - name: Install dependencies
        run: poetry install --sync --no-interaction

      - name: Package project
        run: poetry build

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

Let's break this down a bit.

First, we give the action a name:

name: Release

(Hmm. Maybe 'Publish' would've been better.)

Then we specify when the action will run:

on:
  push:
    tags:
      - '*.*.*'

In this case, it will run each time a new tag is pushed, which we can do when drafting a new release:

Create new tag on release publish option in GitHub
Create new tag on release publish option in GitHub

We also give the action permissions to read the repository code. Write permissions aren't necessary.

permissions:
  contents: read

Next, we're going to set up the job our action will run. The id-token: write permission is important because PyPI trusted publishing involves getting GitHub to create a short-lived ID token to verify that the GitHub action has the authority to publish that Python package.

Note that the name of the environment is the same as the one we specified in the package's PyPI publishing settings and the URL is the package's URL on PyPI:

jobs:
  pypi-publish:
    name: Upload release to PyPI
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/project/blurry-cli/
    permissions:
      id-token: write

Now, we have a few steps to set up Python and install and configure Poetry. We've set Poetry not to use a virtual environment because the workflow environment is already nicely self-contained.

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python 3.10
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Install Poetry
        run: |
          curl -sSL https://install.python-poetry.org | python - -y

      - name: Update PATH
        run: echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Update Poetry configuration
        run: poetry config virtualenvs.create false

Next, we install the package's dependencies and build it:

      - name: Install dependencies
        run: poetry install --sync --no-interaction

      - name: Package project
        run: poetry build

And finally we publish it using the Python Package Authority's official GitHub action for publishing Python packages:

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

When it's set up, your release will publish automatically. The action run will look something like this:

Python package GitHub workflow preview
Python package GitHub workflow preview

Conclusion

So what are you waiting for? Go publish that Python package like it's 2024.