Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions .github/workflows/release_python_binding.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# under the License.

# Publish the paimon Python binding to PyPI.
# Trigger: push tag only (e.g. v0.1.0).
# Trigger: release tag push or manual dispatch.
# Pre-release tags (containing '-') publish to TestPyPI; release tags publish to PyPI.
#
# Token auth: add secrets PYPI_API_TOKEN / TEST_PYPI_API_TOKEN for publishing.
Expand All @@ -29,21 +29,37 @@ on:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
workflow_dispatch:
inputs:
tag:
description: Release tag, for example v0.3.0-rc2
required: true
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ inputs.tag || github.ref_name }}
cancel-in-progress: true

permissions:
contents: read

env:
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}

jobs:
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ env.RELEASE_TAG }}

- name: Set Python release version
run: >
python3 scripts/python_release_version.py
--tag "${RELEASE_TAG}"
--pyproject bindings/python/pyproject.toml

- uses: PyO3/maturin-action@v1
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
with:
working-directory: bindings/python
command: sdist
Expand All @@ -69,8 +85,17 @@ jobs:
- { os: ubuntu-latest, target: "aarch64", manylinux: "manylinux_2_28" }
steps:
- uses: actions/checkout@v7
with:
ref: ${{ env.RELEASE_TAG }}

- name: Set Python release version
shell: bash
run: >
python3 scripts/python_release_version.py
--tag "${RELEASE_TAG}"
Comment thread
XiaoHongbo-Hope marked this conversation as resolved.
--pyproject bindings/python/pyproject.toml

- uses: PyO3/maturin-action@v1
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
with:
working-directory: bindings/python
target: ${{ matrix.target }}
Expand Down Expand Up @@ -143,7 +168,7 @@ jobs:
permissions:
contents: read
needs: [sdist, wheels]
if: startsWith(github.ref, 'refs/tags/')
if: startsWith(inputs.tag || github.ref_name, 'v')
steps:
- uses: actions/download-artifact@v8
with:
Expand All @@ -152,17 +177,17 @@ jobs:
path: bindings/python/dist

- name: Publish to TestPyPI
if: contains(github.ref, '-')
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
if: contains(env.RELEASE_TAG, '-')
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true
packages-dir: bindings/python/dist
password: ${{ secrets.TEST_PYPI_API_TOKEN }}

- name: Publish to PyPI
if: ${{ !contains(github.ref, '-') }}
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
if: ${{ !contains(env.RELEASE_TAG, '-') }}
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
skip-existing: true
packages-dir: bindings/python/dist
Expand Down
2 changes: 1 addition & 1 deletion docs/src/release/verifying-a-release-candidate.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ For any user-facing feature included in a release, we aim to ensure it is functi
- **Python binding:** The RC is published to **TestPyPI**; install the client from TestPyPI and write your own test cases to verify:

```bash
pip install -i https://test.pypi.org/simple/ pypaimon-rust==${RELEASE_VERSION}
pip install -i https://test.pypi.org/simple/ pypaimon-rust==${RELEASE_VERSION}rc${RC_NUM}
```

- **Go binding:** The RC is published as a Go module tag `bindings/go/v${RELEASE_VERSION}-rc${RC_NUM}`; see [Go Binding](https://paimon.apache.org/docs/rust/go-binding/) for usage. Add it to your Go project and write test cases to verify:
Expand Down
82 changes: 82 additions & 0 deletions scripts/python_release_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python3

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Set a unique Python version for release-candidate artifacts."""

from __future__ import annotations

import argparse
import re
import sys
import tomllib
from pathlib import Path


TAG_PATTERN = re.compile(r"^v(?P<base>\d+\.\d+\.\d+)(?:-rc(?P<rc>[1-9]\d*))?$")
Comment thread
XiaoHongbo-Hope marked this conversation as resolved.
DYNAMIC_VERSION = 'dynamic = ["version"]'


def workspace_version(root: Path) -> str:
with (root / "Cargo.toml").open("rb") as source:
return str(tomllib.load(source)["workspace"]["package"]["version"])


def python_version(tag: str, expected_base: str) -> str:
match = TAG_PATTERN.fullmatch(tag)
if match is None:
raise ValueError(f"invalid release tag: {tag}")
base = match.group("base")
if base != expected_base:
raise ValueError(f"tag {base} does not match workspace {expected_base}")
rc = match.group("rc")
return base if rc is None else f"{base}rc{rc}"


def update_pyproject(path: Path, version: str, expected_base: str) -> None:
if version == expected_base:
return
content = path.read_text(encoding="utf-8")
if content.count(DYNAMIC_VERSION) != 1:
raise ValueError(f"unexpected dynamic version setting in {path}")
path.write_text(
content.replace(DYNAMIC_VERSION, f'version = "{version}"', 1),
encoding="utf-8",
)


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--tag", required=True)
parser.add_argument("--pyproject", type=Path)
args = parser.parse_args()

root = Path(__file__).resolve().parents[1]
try:
base = workspace_version(root)
version = python_version(args.tag, base)
if args.pyproject is not None:
update_pyproject(root / args.pyproject, version, base)
except (KeyError, OSError, ValueError, tomllib.TOMLDecodeError) as error:
print(f"Python release version failed: {error}", file=sys.stderr)
return 1

print(version)
return 0


if __name__ == "__main__":
sys.exit(main())
Loading