DEV Community

Cover image for [GitHub Actions] Automate Build and Deployment of Your Python Package to PyPI and GitHub Releases πŸπŸ“¦
Nao
Nao

Posted on

[GitHub Actions] Automate Build and Deployment of Your Python Package to PyPI and GitHub Releases πŸπŸ“¦

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 
Enter fullscreen mode Exit fullscreen mode

The following steps will be automatically executed in order:

  1. Upload to TestPyPI
  2. Upload to PyPI
  3. Create a release on GitHub and distribute the source code

We will create a workflow as shown in the image below.

workflow

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.

Publishing for PyPI

Next, open TestPyPI and create a publisher in the same way.

Use a different environment name than the one used for PyPI.

Publishing for TestPyPI

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*.*.*' 
Enter fullscreen mode Exit fullscreen mode

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/ 
Enter fullscreen mode Exit fullscreen mode

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/ 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 }}" 
Enter fullscreen mode Exit fullscreen mode

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" }, ] 
Enter fullscreen mode Exit fullscreen mode

Create a tag and push it.

git tag v2.0.3 git push origin v2.0.3 
Enter fullscreen mode Exit fullscreen mode

The package will be uploaded to PyPI and TestPyPI.

PyPI

A release will also be created, and you can see the source code distributed in zip and tar.gz formats.

release

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.

References

Top comments (0)