code-specialist / pypi-poetry-publish

Opinionated GitHub action to fully automate publishing packages to PyPI - using Poetry and GitHub releases
MIT License
11 stars 4 forks source link
automation github-action github-actions package packaging packaging-python poetry publishing pypi pypi-package python workflow

pypi-poetry-publish

Opinionated GitHub action to fully automate publishing packages to PyPI - using Poetry and GitHub releases.

We published this action because we use it in our projects and thought it would be useful to others as well. This action is open to any kind of collaboration and contribution - We're happy to receive feedback, issues, pull requests or just kudos.

Features

Jump to example:

Prerequisites

This action assumes you use poetry as your package manager and have the pyproject.toml and poetry.lock files in the root directory of your repository.

my_project/
├─ example_package/
│  ├─ __init__.py
├─ pyproject.toml
├─ poetry.lock

:information_source: If you do not use a custom runner, you may use the builtin functionality GITHUB_TOKEN with write permissions as the ACCESS_TOKEN as seen in the first example. See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow

:warning: We recommend you to use this workflow with the test PyPI registry e.g. PUBLISH_REGISTRY: "https://test.pypi.org/simple/" until you can confirm your workflow works as expected.

Minimal example

name: Build and publish python package

on:
  release:
    types: [ published ]

jobs:
  publish-service-client-package:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Publish PyPi package
        uses: code-specialist/pypi-poetry-publish@v1
        with:
          ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PUBLISH_REGISTRY_PASSWORD: ${{ secrets.PYPI_TOKEN }}

Process

  1. You create a new Release and Tag e.g. 1.0.0 (Trigger)
  2. The action will:
    1. build your package
    2. adjust the version in the pyproject.toml and __init__.py in the package directory according to the tag
    3. publish the package to a PyPI registry

Example process

Before the release

./pyproject.toml

[tool.poetry]
name = "code-specialist-example-package"
version = "0.1.0"
description = "Example package"
authors = ["Code Specialist"]
packages = [{include = "example_package"}]

[tool.poetry.dependencies]
python = "^3.10"

./example_package/init.py

__version__ = "0.1.0"

After GitHub Release 1.0.0

The action will alter the content of your repository to avoid conflicts and version mismatches:

pyproject.toml

[tool.poetry]
name = "code-specialist-example-package"
version = "1.0.0" # adjusted to 1.0.0
description = "Example package"
authors = ["Code Specialist"]
packages = [{include = "example_package"}]

[tool.poetry.dependencies]
python = "^3.10"

./example_package/init.py

__version__ = "1.0.0"  # adjusted to 1.0.0

Inputs

The inputs marked with (✓) are required if the POETRY_DEPENDENCY_REGISTRY_URL is set.

Name Description Mandatory Default
ACCESS_TOKEN GitHub token with write access to the repository, to adjust the version
PUBLISH_REGISTRY_PASSWORD Either a password for the registry user or a token in combination with __token__ as the PUBLISH_REGISTRY_USERNAME
PUBLISH_REGISTRY_USERNAME The username for the pypi registry __token__
PACKAGE_DIRECTORY The directory the package is located in e.g. ./src/, ./example_package './'
POETRY_VERSION The Poetry version to perform the build with 1.1.8
POETRY_CORE_VERSION The Poetry Code version to perform the build with 1.0.4
PYTHON_VERSION The Python version to perform the build with 3.10
BRANCH The branch to publish from master
PUBLISH_REGISTRY The registry to publish to e.g.https://test.pypi.org/simple/ https://pypi.org/simple/
POETRY_DEPENDENCY_REGISTRY_URL Allows to define a custom registry to be used by Poetry for dependency installation e.g. https://pypi.code-specialist.com/simple/
POETRY_DEPENDENCY_REGISTRY_NAME The name used for the custom registry in the dependencies. Must match the name in the pyproject.toml (✓)
POETRY_DEPENDENCY_REGISTRY_USERNAME The username for the custom registry (✓)
POETRY_DEPENDENCY_REGISTRY_PASSWORD The password for the custom registry (✓)
POETRY_CUSTOM_REGISTRY_AUTH The authentication type for the custom registry http-basic

Examples

Each example requires you to:

  1. Create a workflow file e.g. .github/workflows/publish.yaml
  2. Create a new release and tag e.g. 1.0.0 and the action will be triggered and publishes your package

:information_source: If there is a use case you would like to see, please open an issue or a pull request.

Publish to Public PyPI

In order to use the GITHUB_TOKEN you also have to provide permissions for the token. In this case you require the contents:write permission.

publish.yaml

name: Build and publish python package

on:
  release:
    types: [ published ]

jobs:
  publish-service-client-package:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Publish PyPi package
        uses: code-specialist/pypi-poetry-publish@v1
        with:
          PACKAGE_DIRECTORY: "./example-package/"
          PYTHON_VERSION: "3.10"
          ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PUBLISH_REGISTRY_PASSWORD: ${{ secrets.PUBLISH_REGISTRY_PASSWORD }}

Publish Test PyPI

publish.yaml

name: Build and publish python package

on:
  release:
    types: [ published ]

jobs:
  publish-service-client-package:
    runs-on: ubuntu-latest
    steps:
      - name: Publish PyPi package
        uses: code-specialist/pypi-poetry-publish@v1
        with:
          PACKAGE_DIRECTORY: "./example-package/"
          PYTHON_VERSION: "3.10"
          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
          PUBLISH_REGISTRY_PASSWORD: ${{ secrets.PUBLISH_REGISTRY_PASSWORD }}
          PUBLISH_REGISTRY: "https://test.pypi.org/legacy/"

Publish to a Private PyPI

publish.yaml

name: Build and publish python package

on:
  release:
    types: [ published ]

jobs:
  publish-service-client-package:
    runs-on: ubuntu-latest
    steps:
      - name: Publish PyPI package
        uses: code-specialist/pypi-poetry-publish@v1
        with:
          PACKAGE_DIRECTORY: "./example-package/"
          PYTHON_VERSION: "3.10"
          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
          PUBLISH_REGISTRY_PASSWORD: ${{ secrets.PUBLISH_REGISTRY_PASSWORD }}
          PUBLISH_REGISTRY_USER: ${{ secrets.PUBLISH_REGISTRY_USER }}
          PUBLISH_REGISTRY: "https://pypi.code-specialist.com/simple/"

With Private Dependencies

publish.yaml

name: Build and publish python package

on:
  release:
    types: [ published ]

jobs:
  publish-service-client-package:
    runs-on: ubuntu-latest
    steps:
    - name: Publish PyPI package
      uses: code-specialist/pypi-poetry-publish@v1
      with:
        ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
        PUBLISH_REGISTRY_USER: ${{ secrets.PUBLISH_REGISTRY_USER }}
        PUBLISH_REGISTRY_PASSWORD: ${{ secrets.PUBLISH_REGISTRY_PASSWORD }}
        PUBLISH_REGISTRY: "https://pypi.code-specialist.com"
        POETRY_DEPENDENCY_REGISTRY_URL: "https://pypi.code-specialist.com"
        POETRY_DEPENDENCY_REGISTRY_NAME: "codespecialist"
        POETRY_DEPENDENCY_REGISTRY_USERNAME: ${{ secrets.CUSTOM_PUBLISH_REGISTRY_USERNAME }}
        POETRY_DEPENDENCY_REGISTRY_PASSWORD: ${{ secrets.CUSTOM_PUBLISH_REGISTRY_PASSWORD }}                                    

For this to install the dependency private-code-specialist-example-package from https://pypi.code-specialist.com/simple/, the corresponding pyproject.toml would look like this:

[tool.poetry]
name = "code-specialist-example-package"
version = "1.0.0" # adjusted to 1.0.0
description = "Example package"
authors = ["Code Specialist"]
packages = [{include = "example_package"}]

[[tool.poetry.source]]
name = "codespecialist"
url = "https://pypi.code-specialist.com"

[tool.poetry.dependencies]
python = "^3.10"
private-code-specialist-example-package = {version = "^1.0.0", source = "codespecialist"}