Repository navigation
Python(chore): automate the changelog and release version bump #825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
242b664
Python(chore): derive version from git tags and automate the changelog
alexluck-sift 6222aa6
Python(chore): fix docs build and tighten release packaging
alexluck-sift f7c29e6
Python(chore): bump a static version in the release PR
alexluck-sift 77a1e13
Python(chore): stop releases that miss changelog fragments
alexluck-sift 25b84e7
Python(chore): consolidate release checks and fragment rules
alexluck-sift b3018fa
Python(chore): limit token permissions in the publish workflow
alexluck-sift 4e141d4
Python(chore): drop the docs dependency bump
alexluck-sift a53d407
Python(chore): make release prep rerunnable and fix nested CI permiss…
alexluck-sift 9c825b7
Python(chore): retry publish for an existing unpublished tag
alexluck-sift File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| name: python-changelog-check | ||
|
|
||
| # Every pull request that changes python/ must add a changelog fragment in python/changelog.d. | ||
| # An empty <PR>.chore.md fragment marks a change with no user-visible effect. | ||
| # See python/changelog.d/README.md. | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - 'python/**' | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| fragment: | ||
| name: Changelog fragment | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| # HEAD is the merge of the PR into the base branch, and HEAD^1 is the base branch tip. | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 2 | ||
|
|
||
| - name: Set up uv | ||
| uses: astral-sh/setup-uv@v6 | ||
|
|
||
| - name: Check for a fragment | ||
| run: uvx towncrier@26.9.0 check --dir python --config python/pyproject.toml --compare-with HEAD^1 | ||
|
|
||
| # A release PR adds a version heading to the changelog. See python/changelog.d/README.md. | ||
| - name: Check that a release PR includes every fragment | ||
| run: | | ||
| if git diff HEAD^1 HEAD -- python/CHANGELOG.md | grep -q '^+## \[v'; then | ||
| if ! python3 python/scripts/release_version.py --leftover; then | ||
| echo "Error: the fragments above merged after this release PR was opened." | ||
| echo "Run Python Release Prep again to update this PR." | ||
| exit 1 | ||
| fi | ||
| fi | ||
|
|
||
| - name: Check that the fragments render | ||
| run: uvx towncrier@26.9.0 build --dir python --config python/pyproject.toml --draft --version 0.0.0 --date "Draft" > /dev/null |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| name: Python Release Prep | ||
|
|
||
| # Sets the next version in python/pyproject.toml, renders the changelog fragments in | ||
| # python/changelog.d into python/CHANGELOG.md and opens a pull request. Merging that pull request | ||
| # starts the Python Release Tag workflow. | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: Optional explicit X.Y.Z. Skips the automatic bump from the fragment types. | ||
| type: string | ||
| required: false | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| prep: | ||
| name: Open release prep PR | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.repository.default_branch }} | ||
|
|
||
| - name: Set up uv | ||
| uses: astral-sh/setup-uv@v6 | ||
|
|
||
| - name: Compute next version | ||
| id: version | ||
| env: | ||
| VERSION_OVERRIDE: ${{ inputs.version }} | ||
| run: | | ||
| version=$(python3 python/scripts/release_version.py --version "$VERSION_OVERRIDE") | ||
| echo "version=$version" >> "$GITHUB_OUTPUT" | ||
| echo "Next version: $version" | ||
|
|
||
| - name: Set version and render changelog | ||
| env: | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| run: | | ||
| cd python | ||
| uv version --no-sync "$VERSION" | ||
| uvx towncrier@26.9.0 build --yes --version "$VERSION" --date "$(date '+%B %-d, %Y')" | ||
|
|
||
| - name: Open pull request | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| BASE_BRANCH: ${{ github.event.repository.default_branch }} | ||
| run: | | ||
| branch="release/python-v${VERSION}" | ||
| title="Python(chore): v${VERSION} release prep" | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git checkout -b "$branch" | ||
| git add -A python/pyproject.toml python/uv.lock python/CHANGELOG.md python/changelog.d | ||
| git commit -m "$title" | ||
| # A rerun for the same version replaces the branch and updates its open pull request. | ||
| git push --force origin "$branch" | ||
| if [[ "$(gh pr view "$branch" --json state -q .state 2>/dev/null)" == "OPEN" ]]; then | ||
| echo "Updated the open pull request for ${branch}." | ||
| exit 0 | ||
| fi | ||
| gh pr create \ | ||
| --base "$BASE_BRANCH" \ | ||
| --head "$branch" \ | ||
| --title "$title" \ | ||
| --body "Sets the version to ${VERSION} and renders its changelog fragments. Review the version and entries before merging. Merging this pull request tags \`python/v${VERSION}\` and publishes it. See \`python/changelog.d/README.md\`." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| name: Python Release Tag | ||
|
|
||
| # Tags a Python release when python/pyproject.toml on the default branch has a version with no tag | ||
| # yet, then starts the PyPI Publish workflow on that tag. The Python Release Prep workflow opens the | ||
| # pull request that sets the version. | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - 'python/pyproject.toml' | ||
| - 'python/CHANGELOG.md' | ||
|
|
||
| permissions: | ||
| contents: write | ||
| actions: write | ||
|
|
||
| concurrency: | ||
| group: python-release-tag | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| tag: | ||
| name: Create tag and start publish | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| # A tag pushed with the default token does not start other workflows, so this starts the | ||
| # publish workflow explicitly. It requires a tag ref. | ||
| - name: Create tag and start publish | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| tag="python/v$(python3 python/scripts/release_version.py --current)" | ||
| if [[ -n "$(git ls-remote --tags origin "refs/tags/${tag}")" ]]; then | ||
| # A rerun after a failed publish request starts publish for the existing tag. | ||
| if [[ -n "$(gh run list --workflow python_release.yaml --branch "$tag" --limit 1 --json databaseId -q '.[].databaseId')" ]]; then | ||
| echo "Tag ${tag} already exists and its publish has started. Nothing to release." | ||
| exit 0 | ||
| fi | ||
| echo "Tag ${tag} already exists but was never published. Starting publish." | ||
| else | ||
| # See python/changelog.d/README.md. | ||
| if ! python3 python/scripts/release_version.py --leftover; then | ||
| echo "Error: the fragments above are not in the changelog for ${tag}. Not tagging." | ||
| exit 1 | ||
| fi | ||
| git tag "$tag" "$GITHUB_SHA" | ||
| git push origin "refs/tags/${tag}" | ||
| fi | ||
| gh workflow run python_release.yaml --ref "$tag" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ python/docs/sift_py | |
| ### C++ template | ||
| # Prerequisites | ||
| *.d | ||
| !changelog.d | ||
|
|
||
| # Compiled Object files | ||
| *.slo | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Changelog fragments | ||
|
|
||
| Each pull request that changes `python/` adds one fragment file to this directory. A release | ||
| renders the fragments into `CHANGELOG.md` and deletes them. Do not edit `CHANGELOG.md` by hand. | ||
|
|
||
| ## Add a fragment | ||
|
|
||
| Name the file `<PR number>.<type>.md`. From `python/`, run: | ||
|
|
||
| ```bash | ||
| uvx towncrier create 830.feat.md --content "Added \`client.webhooks\`." | ||
| ``` | ||
|
|
||
| | Type | Use for | Version bump | | ||
| | ---------- | -------------------------------- | ------------ | | ||
| | `breaking` | A change that breaks callers | major | | ||
| | `feat` | New user-facing behavior | minor | | ||
| | `fix` | A bug fix users can see | patch | | ||
| | `chore` | No user-visible change | none | | ||
|
|
||
| The version bump follows the highest type in the release. While the major version is 0, a | ||
| `breaking` change bumps the minor version. To release a different version, pass `version` to the | ||
| release workflow. | ||
|
|
||
| Write the entry for a user of the library. Start with what changed, in one sentence. The | ||
| renderer adds the PR link. A fragment can have more paragraphs and code blocks after the first | ||
| sentence. They are indented under the bullet. | ||
|
|
||
| ## PRs with no user-visible change | ||
|
|
||
| Add an empty file named `<PR number>.chore.md`. It is never rendered. A release made only of | ||
| `chore` fragments is rejected. | ||
|
|
||
| ## No PR number yet | ||
|
|
||
| Name the file `+<short-name>.<type>.md`. The entry has no PR link. | ||
|
|
||
| ## Release | ||
|
|
||
| Run the Python Release Prep workflow. It computes the next version from the current version in | ||
| `pyproject.toml` and the fragment types, sets it in `pyproject.toml` and `uv.lock`, renders | ||
| `CHANGELOG.md`, and opens a pull request. After that PR merges, the Python Release Tag workflow | ||
| creates the `python/vX.Y.Z` tag and starts the publish workflow. | ||
|
|
||
| If another change to `python/` merges while the release pull request is open, that change would | ||
| ship without its entry. The changelog check fails the release pull request in that case. Run | ||
| Python Release Prep again. For the same version, it replaces the release branch and updates the | ||
| open pull request. If the new changes raise the version, it opens a new pull request, so close the | ||
| old one. If the release pull request merges anyway, Python Release Tag | ||
| refuses to tag. Move the leftover entries into the new version's section of `CHANGELOG.md`, delete | ||
| their fragments, and merge that change to release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| {% if render_title %} | ||
| ## [v{{ versiondata.version }}] - {{ versiondata.date }} | ||
|
|
||
| {% endif %} | ||
| {% for category in ["breaking", "feat", "fix"] %} | ||
| {% for text, values in sections[""].get(category, {}).items() %} | ||
| {% set lines = text.strip().split("\n", 1) %} | ||
| - {{ lines[0] }}{% if values %} ({{ values|join(", ") }}){% endif %}{{ "" }} | ||
| {% if lines|length > 1 %} | ||
| {{ lines[1] }} | ||
| {% endif %} | ||
| {% endfor %} | ||
| {% endfor %} | ||
| {{ "\n" }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| --8<-- "CHANGELOG.md" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.