From 1cb7eea61255ceb981253e37bac9e535c1b3d224 Mon Sep 17 00:00:00 2001 From: Gracjan Sadowicz Date: Fri, 19 Jun 2026 09:03:20 +0200 Subject: [PATCH] Add PyPI publish CD (release-labelled PR -> build & upload) Adds two GitHub Actions workflows: - prepare-release.yml: manual dispatch; bumps version in setup.py (version=, ravendb-embedded==, ravendb~=) and requirements.txt, then opens a PR labelled "release". - publish.yml: on merge of the labelled PR, builds sdist+wheel, uploads to PyPI via twine, then creates the version git tag + GitHub release. Requires repo secret PYPI_API_TOKEN and the "Allow GitHub Actions to create and approve pull requests" setting enabled. Note: ravendb-test-driver pins ravendb-embedded==, so publish ravendb-embedded first. --- .github/workflows/prepare-release.yml | 74 +++++++++++++++++++++++++++ .github/workflows/publish.yml | 62 ++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 .github/workflows/prepare-release.yml create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..554223c --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,74 @@ +name: Prepare release PR + +# Step 1 of the release flow. +# Manually triggered. Bumps the version across setup.py / requirements.txt and +# opens a PR labelled `release`. Merging that PR fires `publish.yml`. +# +# Note: ravendb-test-driver pins `ravendb-embedded==`, so publish +# ravendb-embedded for this version BEFORE merging this PR. + +on: + workflow_dispatch: + inputs: + version: + description: "Release version, e.g. 7.2.2 (sets package version and the ravendb / ravendb-embedded pins)." + required: true + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Bump version + env: + VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + sed -i -E "s/version=\"[0-9][^\"]*\"/version=\"${VERSION}\"/" setup.py + sed -i -E "s/ravendb-embedded==[0-9][^\"]*/ravendb-embedded==${VERSION}/" setup.py + sed -i -E "s/ravendb~=[0-9][^\"]*/ravendb~=${VERSION}/" setup.py + sed -i -E "s/^ravendb~=.*/ravendb~=${VERSION}/" requirements.txt + sed -i -E "s/^ravendb-embedded==.*/ravendb-embedded==${VERSION}/" requirements.txt + + echo "----- setup.py -----" + grep -nE 'version=|ravendb~=|ravendb-embedded==' setup.py || true + echo "----- requirements.txt -----" + cat requirements.txt + + if git diff --quiet; then + echo "::error::No version strings changed. Check the sed patterns against setup.py / requirements.txt." + exit 1 + fi + + - name: Create release PR + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + base: ${{ github.event.repository.default_branch }} + branch: release/${{ inputs.version }} + delete-branch: true + commit-message: "Release ${{ inputs.version }}" + title: "Release ${{ inputs.version }}" + labels: release + body: | + Automated release for **ravendb-test-driver ${{ inputs.version }}**. + + Bumps `setup.py` (package version, `ravendb` + `ravendb-embedded` pins) and `requirements.txt`. + + > [!WARNING] + > Merging this PR **while the `release` label is set** triggers the + > **Publish to PyPI** workflow, which builds and uploads the package and + > creates the `v${{ inputs.version }}` GitHub release. + + > [!IMPORTANT] + > This package pins `ravendb-embedded==${{ inputs.version }}`. Make sure + > ravendb-embedded ${{ inputs.version }} is already on PyPI before merging. + + Review the diff, then merge to publish. Remove the `release` label before + merging if you do **not** want to publish. diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e6b7401 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,62 @@ +name: Publish to PyPI + +# Step 2 of the release flow. +# Fires when a PR labelled `release` is merged (the PR opened by prepare-release.yml). +# Also runnable manually as an emergency fallback (publishes whatever is on the branch). + +on: + pull_request: + types: [closed] + workflow_dispatch: + +permissions: + contents: write # create the git tag / GitHub release + +jobs: + publish: + # Only on manual run, or on a merged PR that carried the `release` label. + if: >- + github.event_name == 'workflow_dispatch' || + (github.event.pull_request.merged == true && + contains(github.event.pull_request.labels.*.name, 'release')) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.ref || github.ref_name }} + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install build tooling + run: python -m pip install --upgrade pip setuptools wheel twine + + - name: Build sdist + wheel + run: python setup.py sdist bdist_wheel + + - name: Read package version + id: ver + run: echo "version=$(python setup.py --version)" >> "$GITHUB_OUTPUT" + + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload --non-interactive --skip-existing dist/* + + - name: Tag & GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.ver.outputs.version }} + TARGET: ${{ github.event.pull_request.base.ref || github.ref_name }} + run: | + set -euo pipefail + if gh release view "${VERSION}" >/dev/null 2>&1; then + echo "Release ${VERSION} already exists, skipping." + else + gh release create "${VERSION}" \ + --target "${TARGET}" \ + --title "${VERSION}" \ + --notes "ravendb-test-driver ${VERSION} published to PyPI." + fi