Skip to content

Add manual Python release CI with beta support#936

Draft
Dsantra92 wants to merge 1 commit into
mainfrom
feat/pypi
Draft

Add manual Python release CI with beta support#936
Dsantra92 wants to merge 1 commit into
mainfrom
feat/pypi

Conversation

@Dsantra92

@Dsantra92 Dsantra92 commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Split Python release CI into package-specific workflows for potpie-context-engine and root potpie, plus a main orchestrator workflow for all, context-engine, or potpie.
  • Infer beta/rc/final release channel from checked-in source versions instead of taking a manual channel input.
  • Keep context-engine OAuth client ID injection wired through LINEAR_CLIENT_ID and POTPIE_GITHUB_CLIENT_ID; require them before TestPyPI/PyPI context-engine publishes.
  • Publish only the selected package artifacts from each package workflow, with local context-engine artifacts used only for root Potpie smoke installs.
  • Continue using PyPI Trusted Publishing with protected testpypi and pypi environments.

Closes #934

Verification

  • python3 -m py_compile scripts/validate_python_release.py
  • uv run --with packaging python scripts/validate_python_release.py --package context-engine --publish-target build-only --output-dir /tmp/potpie-release-context-check
  • uv run --with packaging python scripts/validate_python_release.py --package potpie --publish-target build-only --output-dir /tmp/potpie-release-potpie-check
  • go run github.com/rhysd/actionlint/cmd/actionlint@latest .github/workflows/release-python.yml .github/workflows/release-context-engine.yml .github/workflows/release-potpie.yml
  • uvx --with build --with twine python -m build --sdist --wheel --outdir /tmp/potpie-release-context-build potpie/context-engine
  • uvx --with build --with twine python -m twine check /tmp/potpie-release-context-build/*
  • uvx --with build --with twine python -m build --sdist --wheel --outdir /tmp/potpie-release-root-deps potpie/context-engine
  • uvx --with build --with twine python -m build --sdist --wheel --outdir /tmp/potpie-release-root-build .
  • uvx --with build --with twine python -m twine check /tmp/potpie-release-root-deps/* /tmp/potpie-release-root-build/*
  • Signed commit verified locally with git log -1 --show-signature

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new workflow_dispatch-triggered GitHub Actions pipeline (release-python.yml) for building and publishing Python packages, along with a preflight validation script (scripts/validate_python_release.py). The script validates package versions, dependency pins, channel rules, and PyPI availability. The workflow builds pure and Rust-backed distributions, smoke-installs them, assembles checksummed artifacts, and conditionally publishes to TestPyPI or PyPI.

Changes

Python Release CI Pipeline

Layer / File(s) Summary
Release validation script
scripts/validate_python_release.py
New CLI script that loads package versions from pyproject.toml and Cargo.toml, validates PEP 440 conformance and channel-specific prerelease rules (beta/rc/final), checks the potpie-context-engine dependency specifier, enforces pypi-target publish-policy constraints (tag ref, confirm_publish), queries PyPI/TestPyPI for version conflicts, writes release-metadata.json, and appends GitHub Actions output variables.
Workflow: dispatch inputs, preflight, and build jobs
.github/workflows/release-python.yml (lines 1–226)
Defines workflow_dispatch inputs (publish target, channel, optional version guards, confirmation), concurrency cancellation, and environment variables. Adds the preflight job that runs the validation script and uploads metadata, build-pure for sdist/wheel of pure Python packages, build-parsing using cibuildwheel across a Linux/macOS OS-arch matrix, and build-parsing-sdist for the parsing source distribution.
Workflow: smoke-install, assemble, and publish jobs
.github/workflows/release-python.yml (lines 227–382)
Adds smoke-install which installs the built distributions into a virtualenv, verifies the Rust module import, and runs a dry-run potpie setup against a temporary git repository. assemble-artifacts merges all distribution artifacts with release metadata and SHA256SUMS.txt. Conditional publish-testpypi and publish-pypi jobs publish via pypa/gh-action-pypi-publish using OIDC Trusted Publishing.

Sequence Diagram(s)

sequenceDiagram
  actor Dev as Developer
  participant GHA as GitHub Actions
  participant Preflight as preflight job
  participant ValidScript as validate_python_release.py
  participant PyPIAPI as PyPI / TestPyPI JSON API
  participant BuildJobs as build-pure / build-parsing / build-parsing-sdist
  participant Smoke as smoke-install job
  participant Assemble as assemble-artifacts job
  participant Registry as TestPyPI / PyPI

  Dev->>GHA: workflow_dispatch (publish_target, channel, versions, confirm)
  GHA->>Preflight: run preflight job
  Preflight->>ValidScript: python validate_python_release.py --publish-target --channel ...
  ValidScript->>PyPIAPI: GET /pypi/{name}/{version}/json (conflict check)
  PyPIAPI-->>ValidScript: 404 not found / 200 exists / error
  ValidScript-->>Preflight: release-metadata.json + GITHUB_OUTPUT vars
  Preflight-->>GHA: upload release-metadata artifact
  GHA->>BuildJobs: build pure sdist/wheels + parsing wheels (cibuildwheel matrix) + parsing sdist
  BuildJobs-->>GHA: upload dist-pure, dist-parsing-*, dist-parsing-sdist artifacts
  GHA->>Smoke: download built dists, install potpie, import parsing module, run potpie setup --dry-run
  Smoke-->>GHA: smoke pass / fail
  GHA->>Assemble: download all dists + metadata, generate SHA256SUMS.txt
  Assemble-->>GHA: upload release-artifacts bundle
  GHA->>Registry: pypa/gh-action-pypi-publish (dist/) via OIDC id-token
  Registry-->>GHA: publish confirmed
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: a manual Python release workflow with beta support.
Linked Issues check ✅ Passed The changes implement the requested manual release CI, version validation, build targets, smoke tests, and Trusted Publishing.
Out of Scope Changes check ✅ Passed The pull request stays focused on the release workflow and validator script with no clear unrelated additions.
Description check ✅ Passed The description is clearly about the release workflow and validation script changes, matching the PR’s release-publishing focus.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Dsantra92 Dsantra92 marked this pull request as ready for review June 24, 2026 08:41
@Dsantra92 Dsantra92 marked this pull request as draft June 24, 2026 08:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release-python.yml:
- Around line 79-88: The Validate release metadata step is interpolating
dispatch inputs directly into the shell command, which can be unsafe. Move the
github.event.inputs values into env on the same step, then update the run
command to pass only shell-expanded variables to
scripts/validate_python_release.py; keep the existing argument names but
reference the env vars instead of inline expressions. Use the Validate release
metadata step as the place to fix this and preserve the current validate script
invocation behavior.
- Around line 261-270: The smoke test currently installs potpie with
--find-links dist but can still resolve potpie-context-engine and potpie-parsing
from PyPI, so update the release-python workflow’s Smoke install and run CLI
step to explicitly install and pin the three local built artifacts from dist
using the preflight version output. Make sure the workflow verifies the built
wheel versions for potpie, potpie-context-engine, and potpie-parsing before
running potpie status, so the smoke test only exercises the artifacts produced
in this release job.

In `@scripts/validate_python_release.py`:
- Around line 86-105: Reject .devN versions in validate_pep440 by checking
version.is_devrelease before the beta/rc-specific pre-release validation. Update
validate_pep440 in scripts/validate_python_release.py so the
PackageInfo.parsed_version handling fails for beta and rc channels when a dev
release is present, alongside the existing version.pre checks, while leaving the
final-channel logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 349a78af-95f4-4878-ac62-05cddc3217a1

📥 Commits

Reviewing files that changed from the base of the PR and between 8c2d243 and b69b33c.

📒 Files selected for processing (2)
  • .github/workflows/release-python.yml
  • scripts/validate_python_release.py

Comment thread .github/workflows/release-python.yml Outdated
Comment on lines +79 to +88
- name: Validate release metadata
id: validate
run: >-
python scripts/validate_python_release.py
--publish-target "${{ github.event.inputs.publish_target }}"
--channel "${{ github.event.inputs.channel }}"
--expected-potpie-version "${{ github.event.inputs.expected_potpie_version }}"
--expected-context-engine-version "${{ github.event.inputs.expected_context_engine_version }}"
--expected-parsing-version "${{ github.event.inputs.expected_parsing_version }}"
--confirm-publish "${{ github.event.inputs.confirm_publish }}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Move dispatch inputs through environment variables before invoking the shell.

Line 85 through Line 88 interpolate user-controlled string inputs directly into run; an input containing quotes/semicolons can break out of the quoted argument. Pass expressions via env and expand shell variables instead.

Proposed fix
       - name: Validate release metadata
         id: validate
+        env:
+          PUBLISH_TARGET: ${{ github.event.inputs.publish_target }}
+          CHANNEL: ${{ github.event.inputs.channel }}
+          EXPECTED_POTPIE_VERSION: ${{ github.event.inputs.expected_potpie_version }}
+          EXPECTED_CONTEXT_ENGINE_VERSION: ${{ github.event.inputs.expected_context_engine_version }}
+          EXPECTED_PARSING_VERSION: ${{ github.event.inputs.expected_parsing_version }}
+          CONFIRM_PUBLISH: ${{ github.event.inputs.confirm_publish }}
         run: >-
           python scripts/validate_python_release.py
-          --publish-target "${{ github.event.inputs.publish_target }}"
-          --channel "${{ github.event.inputs.channel }}"
-          --expected-potpie-version "${{ github.event.inputs.expected_potpie_version }}"
-          --expected-context-engine-version "${{ github.event.inputs.expected_context_engine_version }}"
-          --expected-parsing-version "${{ github.event.inputs.expected_parsing_version }}"
-          --confirm-publish "${{ github.event.inputs.confirm_publish }}"
+          --publish-target "$PUBLISH_TARGET"
+          --channel "$CHANNEL"
+          --expected-potpie-version "$EXPECTED_POTPIE_VERSION"
+          --expected-context-engine-version "$EXPECTED_CONTEXT_ENGINE_VERSION"
+          --expected-parsing-version "$EXPECTED_PARSING_VERSION"
+          --confirm-publish "$CONFIRM_PUBLISH"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Validate release metadata
id: validate
run: >-
python scripts/validate_python_release.py
--publish-target "${{ github.event.inputs.publish_target }}"
--channel "${{ github.event.inputs.channel }}"
--expected-potpie-version "${{ github.event.inputs.expected_potpie_version }}"
--expected-context-engine-version "${{ github.event.inputs.expected_context_engine_version }}"
--expected-parsing-version "${{ github.event.inputs.expected_parsing_version }}"
--confirm-publish "${{ github.event.inputs.confirm_publish }}"
- name: Validate release metadata
id: validate
env:
PUBLISH_TARGET: ${{ github.event.inputs.publish_target }}
CHANNEL: ${{ github.event.inputs.channel }}
EXPECTED_POTPIE_VERSION: ${{ github.event.inputs.expected_potpie_version }}
EXPECTED_CONTEXT_ENGINE_VERSION: ${{ github.event.inputs.expected_context_engine_version }}
EXPECTED_PARSING_VERSION: ${{ github.event.inputs.expected_parsing_version }}
CONFIRM_PUBLISH: ${{ github.event.inputs.confirm_publish }}
run: >-
python scripts/validate_python_release.py
--publish-target "$PUBLISH_TARGET"
--channel "$CHANNEL"
--expected-potpie-version "$EXPECTED_POTPIE_VERSION"
--expected-context-engine-version "$EXPECTED_CONTEXT_ENGINE_VERSION"
--expected-parsing-version "$EXPECTED_PARSING_VERSION"
--confirm-publish "$CONFIRM_PUBLISH"
🧰 Tools
🪛 zizmor (1.26.1)

[error] 83-83: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 84-84: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 85-85: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 86-86: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 87-87: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 88-88: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-python.yml around lines 79 - 88, The Validate
release metadata step is interpolating dispatch inputs directly into the shell
command, which can be unsafe. Move the github.event.inputs values into env on
the same step, then update the run command to pass only shell-expanded variables
to scripts/validate_python_release.py; keep the existing argument names but
reference the env vars instead of inline expressions. Use the Validate release
metadata step as the place to fix this and preserve the current validate script
invocation behavior.

Source: Linters/SAST tools

Comment thread .github/workflows/release-python.yml Outdated
Comment on lines +261 to +270
- name: Smoke install and run CLI
env:
POTPIE_VERSION: ${{ needs.preflight.outputs.potpie_version }}
run: |
ls -la dist
python -m venv .release-smoke
.release-smoke/bin/python -m pip install --upgrade pip
.release-smoke/bin/python -m pip install --find-links dist "potpie==${POTPIE_VERSION}"
.release-smoke/bin/python -c "import parsing.parsing_rs"
PATH="$PWD/.release-smoke/bin:$PATH" potpie status

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Force the smoke test to install the built local artifacts.

Line 268 uses --find-links dist without constraining dependency resolution, so pip can still satisfy potpie-context-engine or potpie-parsing from PyPI instead of the artifacts built in this workflow. Pin and verify the three built package versions before publishing.

Proposed fix
         env:
           POTPIE_VERSION: ${{ needs.preflight.outputs.potpie_version }}
+          CONTEXT_ENGINE_VERSION: ${{ needs.preflight.outputs.context_engine_version }}
+          PARSING_VERSION: ${{ needs.preflight.outputs.parsing_version }}
         run: |
           ls -la dist
           python -m venv .release-smoke
           .release-smoke/bin/python -m pip install --upgrade pip
-          .release-smoke/bin/python -m pip install --find-links dist "potpie==${POTPIE_VERSION}"
+          .release-smoke/bin/python -m pip install --find-links dist "potpie==${POTPIE_VERSION}"
+          .release-smoke/bin/python -m pip install --no-index --find-links dist --no-deps --force-reinstall \
+            "potpie==${POTPIE_VERSION}" \
+            "potpie-context-engine==${CONTEXT_ENGINE_VERSION}" \
+            "potpie-parsing==${PARSING_VERSION}"
+          .release-smoke/bin/python -m pip check
+          .release-smoke/bin/python - <<'PY'
+          import importlib.metadata as md
+          import os
+
+          expected = {
+              "potpie": os.environ["POTPIE_VERSION"],
+              "potpie-context-engine": os.environ["CONTEXT_ENGINE_VERSION"],
+              "potpie-parsing": os.environ["PARSING_VERSION"],
+          }
+          for name, version in expected.items():
+              actual = md.version(name)
+              assert actual == version, f"{name}: expected {version}, got {actual}"
+          PY
           .release-smoke/bin/python -c "import parsing.parsing_rs"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Smoke install and run CLI
env:
POTPIE_VERSION: ${{ needs.preflight.outputs.potpie_version }}
run: |
ls -la dist
python -m venv .release-smoke
.release-smoke/bin/python -m pip install --upgrade pip
.release-smoke/bin/python -m pip install --find-links dist "potpie==${POTPIE_VERSION}"
.release-smoke/bin/python -c "import parsing.parsing_rs"
PATH="$PWD/.release-smoke/bin:$PATH" potpie status
- name: Smoke install and run CLI
env:
POTPIE_VERSION: ${{ needs.preflight.outputs.potpie_version }}
CONTEXT_ENGINE_VERSION: ${{ needs.preflight.outputs.context_engine_version }}
PARSING_VERSION: ${{ needs.preflight.outputs.parsing_version }}
run: |
ls -la dist
python -m venv .release-smoke
.release-smoke/bin/python -m pip install --upgrade pip
.release-smoke/bin/python -m pip install --find-links dist "potpie==${POTPIE_VERSION}"
.release-smoke/bin/python -m pip install --no-index --find-links dist --no-deps --force-reinstall \
"potpie==${POTPIE_VERSION}" \
"potpie-context-engine==${CONTEXT_ENGINE_VERSION}" \
"potpie-parsing==${PARSING_VERSION}"
.release-smoke/bin/python -m pip check
.release-smoke/bin/python - <<'PY'
import importlib.metadata as md
import os
expected = {
"potpie": os.environ["POTPIE_VERSION"],
"potpie-context-engine": os.environ["CONTEXT_ENGINE_VERSION"],
"potpie-parsing": os.environ["PARSING_VERSION"],
}
for name, version in expected.items():
actual = md.version(name)
assert actual == version, f"{name}: expected {version}, got {actual}"
PY
.release-smoke/bin/python -c "import parsing.parsing_rs"
PATH="$PWD/.release-smoke/bin:$PATH" potpie status
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-python.yml around lines 261 - 270, The smoke test
currently installs potpie with --find-links dist but can still resolve
potpie-context-engine and potpie-parsing from PyPI, so update the release-python
workflow’s Smoke install and run CLI step to explicitly install and pin the
three local built artifacts from dist using the preflight version output. Make
sure the workflow verifies the built wheel versions for potpie,
potpie-context-engine, and potpie-parsing before running potpie status, so the
smoke test only exercises the artifacts produced in this release job.

Comment thread scripts/validate_python_release.py Outdated
Comment on lines +86 to +105
def validate_pep440(packages: list[PackageInfo], channel: str) -> None:
for package in packages:
try:
version = package.parsed_version
except InvalidVersion as exc:
fail(f"{package.name} version {package.version!r} is not PEP 440: {exc}")
if version.local is not None:
fail(f"{package.name} version {package.version!r} must not use a local '+...' segment")

if channel == "beta":
if version.pre is None or version.pre[0] != "b":
fail(f"{package.name} must use a beta version like 1.2.0b1")
if "b" not in package.version.lower() or "beta" in package.version.lower():
fail(f"{package.name} beta version must use compact bN syntax, not {package.version!r}")
elif channel == "rc":
if version.pre is None or version.pre[0] != "rc":
fail(f"{package.name} must use a release-candidate version like 1.2.0rc1")
elif channel == "final":
if version.is_prerelease or version.is_devrelease:
fail(f"{package.name} must use a final release version for channel=final")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
from packaging.version import Version

for raw in ("1.2.0b1.dev1", "1.2.0rc1.dev1"):
    version = Version(raw)
    print(raw, "pre=", version.pre, "is_devrelease=", version.is_devrelease)
PY

Repository: potpie-ai/potpie

Length of output: 250


Reject .devN versions for beta/rc channels. version.pre still matches 1.2.0b1.dev1 and 1.2.0rc1.dev1, so add a version.is_devrelease check before the channel-specific beta/rc validation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/validate_python_release.py` around lines 86 - 105, Reject .devN
versions in validate_pep440 by checking version.is_devrelease before the
beta/rc-specific pre-release validation. Update validate_pep440 in
scripts/validate_python_release.py so the PackageInfo.parsed_version handling
fails for beta and rc channels when a dev release is present, alongside the
existing version.pre checks, while leaving the final-channel logic unchanged.

@Dsantra92 Dsantra92 added this to the v2.0.1 milestone Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add manual Python release CI with beta support

1 participant