When releasing a custom library, it can be tedious to manually build it locally, upload it to TestPyPI for verification, then upload it to PyPI, and finally tag and release itβ¦
So, I decided to automate the deployment using GitHub Actions.
Objective of This Article
When you push a tag to the remote repository, this setup will automatically upload your package to PyPI and create a GitHub release.
After you push a tag from your local environmentβ¦
git tag vX.X.X
git push origin vX.X.X
The following steps will be automatically executed in order:
- Upload to TestPyPI
- Upload to PyPI
- Create a release on GitHub and distribute the source code
We will create a workflow as shown in the image below.
Steps
Preparation
Note: Instead of using setup.py as the packaging tool configuration file, we will use pyproject.toml. According to PEP-518, pyproject.toml is recommended for modern Python projects.
Prepare your Python package and upload it as a project to both PyPI and TestPyPI.
If you already have a custom library on PyPI, you can skip this step.
Follow the tutorial below to upload a sample package to TestPyPI and PyPI:
https://packaging.python.org/ja/latest/tutorials/packaging-projects/#creating-a-license
Creating a Workflow File in GitHub Actions
In your repository, go to Actions
> Set up a workflow yourself
> Create a YAML file with any name (in this case, main.yaml) and commit it.
Itβs okay if the file is empty initially.
Setting Up Publishing in PyPI
Traditionally, to upload a package from GitHub Actions to PyPI, you would need to create an API token in PyPI and store that token as a secret in GitHub.
However, by using Publishing, you can automatically issue and authenticate a temporary token when connecting to pre-configured services (including specific users, repositories, etc.)!
Publishing is easier to set up, and since the token has a short expiration time, it also offers better security.
From your project page on PyPI, select Publishing
> Add a new publisher
.
Fill in the fields as shown in the image below and click Add
.
Next, open TestPyPI and create a publisher in the same way.
Use a different environment name than the one used for PyPI.
Workflow in GitHub Actions
Now letβs write the workflow in the YAML file you just created.
1. Start the Workflow When a Tag is Pushed
name: Publish Python π distribution π¦ to PyPI and TestPyPI
on:
push:
tags:
- 'v*.*.*'
2. Build
To build the package, pyproject.toml must exist in the repository, so make sure it is not included in .gitignore.
jobs:
build:
name: Build distribution π¦
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # Checkout the code
- name: Set up Python
uses: actions/setup-python@v5 # Set up the Python environment
with:
python-version: "3.x"
- name: Install pypa/build # Install the build tool
run: >-
python3 -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build # Build the package
- name: Store the distribution packages # Temporarily store the build artifacts in the dist directory under the name python-package-distributions
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/
3. Publish to TestPyPI
publish-to-testpypi:
name: Publish Python π distribution π¦ to TestPyPI
needs:
- build # Start the job only if the build job has completed
runs-on: ubuntu-latest
environment:
name: testpypi # Enter the environment name set in the Publisher
url: https://test.pypi.org/p/example-package-hanaosan0318 # Project URL
permissions:
id-token: write # Grant Publishing permissions
steps:
- name: Download all the dists # Download the build artifacts that were saved earlier
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish distribution π¦ to TestPyPI # Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
4. Publish to PyPI
publish-to-pypi:
name: >-
Publish Python π distribution π¦ to PyPI
needs:
- publish-to-testpypi # Start the job only if the TestPyPI publishing job has completed
runs-on: ubuntu-latest
environment:
name: pypi # Enter the environment name set in the Publisher
url: https://pypi.org/p/example-package-hanaosan0318 # Project URL
permissions:
id-token: write
steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish distribution π¦ to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
5. Create a GitHub Release
github-release:
name: >-
Create GitHub Release with source code
needs:
- publish-to-pypi # Start the job only if the PyPI publishing job has completed
runs-on: ubuntu-latest
permissions:
contents: write # Grant permission to create a GitHub release
steps:
- name: Checkout code
uses: actions/checkout@v4 # Checkout the code
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # A temporary token that is automatically generated each time the workflow is run
run: >-
gh release create
'${{ github.ref_name }}'
--repo '${{ github.repository }}'
--notes "Release for version ${{ github.ref_name }}"
Finally, you have defined the workflow!
You can view the entire YAML file at the following link:
https://github.com/hanaosan/ci-cd-practice-python-library/blob/main/.github/workflows/main.yml
Deploying
Update the version in pyproject.toml to the release version and push the changes.
[project]
name = "example_package_hanaosan0318"
version = "2.0.3" # Change to the release version
authors = [
{ name="Example Author", email="author@example.com" },
]
Create a tag and push it.
git tag v2.0.3
git push origin v2.0.3
The package will be uploaded to PyPI and TestPyPI.
A release will also be created, and you can see the source code distributed in zip and tar.gz formats.
And thatβs it!
You have now automated the process of uploading your package to PyPI and creating a release on GitHub when you push a tag to the remote repository.
Top comments (0)