How to publish a Python package with GitHub Actions using Poetry
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:
The settings I used to publish Blurry, my little static site generator, are:
- Owner:
blurry-dev
- Repository name:
blurry
- Workflow name:
publish.yml
- Environment name:
pypi
When the settings are set up in PyPI, the trusted publishing settings will look like this:
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:
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:
Conclusion
So what are you waiting for? Go publish that Python package like it's 2024.