Skip to content

Commit

Permalink
ci(release/py): add GitHub Actions workflow for releasing Python pack…
Browse files Browse the repository at this point in the history
…age (#19)

* ci(release/py): add GitHub Actions workflow for releasing Python package

* chore: set project metadata for workspace python packages

* chore: add `uv lock` pre-commit hook
  • Loading branch information
WSH032 authored Dec 21, 2024
1 parent 08b66a5 commit f03775e
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 22 deletions.
89 changes: 89 additions & 0 deletions .github/workflows/publish-py.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# refer to: https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/

# WARNING: Do not change the name of this file, keep `publish-py.yml`.
# "trusted publishing" will check the name of the workflow file.

name: Publish Python 🐍 distribution 📦 to PyPI

on:
push:
tags:
# e.g., py/pyo3-utils/v0.1.0
- py/*/v*

defaults:
run:
shell: bash

# NOTE: It's better not to use cache for release workflow.
jobs:
build-dist:
name: Build distribution 📦
runs-on: ubuntu-latest
outputs:
package: ${{ steps.dry-run.outputs.package }}
steps:
- uses: actions/checkout@v4
- name: setup-envs-ver
id: setup-envs-ver
uses: ./.github/actions/setup-envs-ver

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
# see: <https://docs.astral.sh/uv/guides/integration/github/>
version: ${{ steps.setup-envs-ver.outputs.uv }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"

- name: Build a binary wheel and a source tarball (e.g., dry-run)
id: dry-run
run: python ./release.py ${{ github.ref }}

- name: Store the distribution packages
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/
if-no-files-found: error

publish-to-pypi:
needs:
- build-dist
name: Publish Python 🐍 distribution 📦 to PyPI
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/${{ needs.build-dist.outputs.package }}
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
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

github-release:
needs:
- publish-to-pypi
name: Create GitHub release 🏷️
runs-on: ubuntu-latest
permissions:
contents: write # IMPORTANT: mandatory for creating release
steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Create release
uses: ncipollo/release-action@v1
with:
draft: true
body: ${{ github.event.head_commit.message }}
artifacts: dist/*.whl,dist/*.tar.gz
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ repos:
additional_dependencies: ["@commitlint/config-conventional"]
- repo: local
hooks:
# ref: <https://github.com/astral-sh/uv-pre-commit/blob/97775dd8479651d7bc0f21fc2d0b960de1ef6267/.pre-commit-hooks.yaml#L11-L20>
- id: uv-lock
name: uv-lock
language: system
files: ^(uv\.lock|pyproject\.toml|uv\.toml)$
entry: uv lock
pass_filenames: false
- id: python-fmt
stages: [pre-commit]
name: python format
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ test = [
]
tools = ["pre-commit == 4.0.1"]
# NOTE: when adding workspace members,
# remember to update `preload_modules` in `mkdocs.yml`.
# remember to update `preload_modules` in `mkdocs.yml`,
# and `[tool.coverage.run.source]` in `pyproject.toml`.
workspaces = [
"codelldb",
"pyfuture",
Expand Down
7 changes: 6 additions & 1 deletion python/codelldb/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
[project]
name = "codelldb"
version = "0.1.0"
version = "0.1.0-beta.0"
readme = "README.md"
license = "Apache-2.0"
authors = [
{ name = "Sean Wang", email = "[email protected]" },
]
description = "Launch CodeLLDB in debugpy to debug rust code."
requires-python = ">=3.9"

# NOTE: DO NOT use third-party libraries in this file,
Expand Down
8 changes: 7 additions & 1 deletion python/pyo3-utils/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
[project]
name = "pyo3-utils"
version = "0.1.0"
version = "0.1.0-beta.0"
readme = "README.md"
license = "Apache-2.0"
authors = [
{ name = "Sean Wang", email = "[email protected]" },
]
description = "Python integration for the pyo3-utils crate."
requires-python = ">=3.9"

# <https://iscinumpy.dev/post/bound-version-constraints/>
dependencies = ["typing-extensions >= 4"]

[build-system]
Expand Down
18 changes: 16 additions & 2 deletions python/pytauri-plugin-notification/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
[project]
name = "pytauri-plugin-notification"
version = "0.1.0"
version = "0.1.0-beta.0"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.9"
dependencies = ["typing-extensions >= 4"]
authors = [
{ name = "Sean Wang", email = "[email protected]" },
]
description = "tauri-plugin-notification binding for PyTauri."

# <https://iscinumpy.dev/post/bound-version-constraints/>
dependencies = [
"typing-extensions >= 4",
# workspaces, must use `==`
"pytauri == 0.1.*",
]

[tool.uv.sources]
pytauri = { workspace = true }

[build-system]
requires = ["hatchling"]
Expand Down
21 changes: 14 additions & 7 deletions python/pytauri/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
[project]
name = "pytauri"
version = "0.1.0"
version = "0.1.0-beta.0"
readme = "README.md"
license = "Apache-2.0"
authors = [
{ name = "Sean Wang", email = "[email protected]" },
]
requires-python = ">=3.9"
description = "Tauri binding for Python through Pyo3."

# <https://iscinumpy.dev/post/bound-version-constraints/>
dependencies = [
"pydantic == 2.*",
"anyio == 4.*",
"pydantic >= 2",
"anyio >= 4",
"typing-extensions >= 4",
# See: <https://pypi.org/project/backports.entry-points-selectable/>
# and: <https://docs.python.org/3/library/importlib.metadata.html#entry-points>
# Deprecated: once we no longer support versions Python 3.9, we can remove this dependency.
"importlib-metadata >= 3.6",
# workspaces
"pyo3-utils",
"importlib-metadata >= 8",
# workspaces, must use `==`
"pyo3-utils == 0.1.*",
]

[tool.uv.sources]
pyo3-utils= { workspace = true }
pyo3-utils = { workspace = true }

[build-system]
requires = ["hatchling"]
Expand Down
35 changes: 33 additions & 2 deletions release.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from enum import Enum
from logging import basicConfig, getLogger
from os import getenv
from typing import NamedTuple
from typing import NamedTuple, NoReturn

logger = getLogger(__name__)

Expand Down Expand Up @@ -67,7 +67,18 @@ def write_to_github_output(self) -> None:
)


_ASSERT_NEVER_REPR_MAX_LENGTH = 100


def _assert_never(arg: NoReturn, /) -> NoReturn:
value = repr(arg)
if len(value) > _ASSERT_NEVER_REPR_MAX_LENGTH:
value = value[:_ASSERT_NEVER_REPR_MAX_LENGTH] + "..."
raise AssertionError(f"Expected code to be unreachable, but got: {value}")


async def release_rs(package: str, no_dry_run: bool) -> int:
# <https://doc.rust-lang.org/cargo/reference/publishing.html>
args = ["publish", "--all-features", "--package", package, "--color", "always"]
if no_dry_run:
args.append("--no-verify")
Expand All @@ -86,6 +97,21 @@ async def release_rs(package: str, no_dry_run: bool) -> int:
return proc.returncode


async def release_py(package: str, no_dry_run: bool) -> int:
# https://docs.astral.sh/uv/guides/publish/
args = ["build", "--package", package, "--no-sources", "--color", "always"]
if no_dry_run:
raise RuntimeError(
"python package should only be released by `pypa/gh-action-pypi-publish`"
)

proc = await create_subprocess_exec("uv", *args)
await proc.wait()

assert proc.returncode is not None
return proc.returncode


if __name__ == "__main__":
basicConfig(level="INFO")

Expand All @@ -104,6 +130,11 @@ async def release_rs(package: str, no_dry_run: bool) -> int:
async def main() -> int:
if release_tag.kind == Kind.RS:
return await release_rs(release_tag.package, no_dry_run)
raise NotImplementedError()
elif release_tag.kind == Kind.PY:
return await release_py(release_tag.package, no_dry_run)
elif release_tag.kind == Kind.JS:
raise NotImplementedError()
else:
_assert_never(release_tag.kind)

sys.exit(asyncio.run(main()))
20 changes: 12 additions & 8 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f03775e

Please sign in to comment.