This is an automated email from the ASF dual-hosted git repository.

XiaoHongbo-Hope pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new a9a8f6b2 build(python): give RC artifacts unique versions (#557)
a9a8f6b2 is described below

commit a9a8f6b2b85e33ce759851860ac084d19439300a
Author: XiaoHongbo <[email protected]>
AuthorDate: Tue Jul 21 16:54:56 2026 +0800

    build(python): give RC artifacts unique versions (#557)
    
    * build(python): derive RC artifact version from tag
    
    * build(python): harden release workflow
    
    * fix(python): honor dispatched release tag
    
    * fix(python): align manual RC releases
---
 .github/workflows/release_python_binding.yml      | 43 +++++++++---
 docs/src/release/verifying-a-release-candidate.md |  2 +-
 scripts/python_release_version.py                 | 82 +++++++++++++++++++++++
 3 files changed, 117 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/release_python_binding.yml 
b/.github/workflows/release_python_binding.yml
index ea61789c..c6b0b89b 100644
--- a/.github/workflows/release_python_binding.yml
+++ b/.github/workflows/release_python_binding.yml
@@ -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.
@@ -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
@@ -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}"
+          --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 }}
@@ -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:
@@ -152,8 +177,8 @@ 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
@@ -161,8 +186,8 @@ jobs:
           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
diff --git a/docs/src/release/verifying-a-release-candidate.md 
b/docs/src/release/verifying-a-release-candidate.md
index 550a9dae..b8c5c896 100644
--- a/docs/src/release/verifying-a-release-candidate.md
+++ b/docs/src/release/verifying-a-release-candidate.md
@@ -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:
diff --git a/scripts/python_release_version.py 
b/scripts/python_release_version.py
new file mode 100644
index 00000000..58b99b26
--- /dev/null
+++ b/scripts/python_release_version.py
@@ -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*))?$")
+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())

Reply via email to