Skip to content

Migrate to uv and integrate PSR for automated releases#218

Merged
jondricek merged 31 commits intomainfrom
semantic-release
Feb 13, 2026
Merged

Migrate to uv and integrate PSR for automated releases#218
jondricek merged 31 commits intomainfrom
semantic-release

Conversation

@seansica
Copy link
Contributor

@seansica seansica commented Dec 23, 2025

Changes

  • Replace poetry with uv
  • Add pre-commit
    • pre-commit runs ruff-format for formatting files
      • The changes to Python files in this PR are just ruff formatting applied
    • pre-commit runs commitizen for enforcing conventional commit syntax before commits are accepted
  • Add Python Semantic Release (PSR) for automating SemVer tagging and GitHub releases
    • Note: PSR is a highly popular fork of the original semantic-release tool
    • Though semantic-release can technically be used within any language environment, it relies on community plugins, and there are only two plugins listed on the official plugins page that support Python or uv; neither of which have many GitHub Stars.
    • PSR, on the other hand, seems to have a thriving community, and is purpose-built specifically for Python.
    • PSR can run locally and on CI/CD pipelines.
    • The only thing that PSR cannot do is commit linting (e.g., "check every commit since the last release"). For this, we use commitizen. This is the equivalent of using semantic-release and commitlint in our other JS-oriented projects.
  • Refactor CI/CD pipeline to build/publish with uv and PSR (un-tested)
  • Add Justfile for streamlining common functions (equivalent of npm run ... $$\rightarrow$$ just ...)

- Replace Poetry with uv for dependency management and packaging
- Add pre-commit hooks for ruff (lint/format) and commitizen (commit-msg)
- Configure Python Semantic Release (PSR) for automated versioning
- Add commitizen for local commit message validation
- Create unified CI workflow with lint, test, and release jobs
- Add Justfile for common development workflows
@seansica seansica self-assigned this Dec 23, 2025
Copy link
Contributor

@jondricek jondricek left a comment

Choose a reason for hiding this comment

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

Ok, here's my overarching review. It is definitely going in the right direction but has a few edges that need sanding off.


- name: Check commit messages
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
Copy link
Contributor

Choose a reason for hiding this comment

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

I want to make sure we make it as easy as possible for external contributors to contribute. This makes me think of this FAQ from the Conventional Commits site.

https://www.conventionalcommits.org/en/v1.0.0/#do-all-my-contributors-need-to-use-the-conventional-commits-specification

Do all my contributors need to use the Conventional Commits specification?
No! If you use a squash based workflow on Git lead maintainers can clean up the commit messages as they’re merged—adding no workload to casual committers. A common workflow for this is to have your git system automatically squash commits from a pull request and present a form for the lead maintainer to enter the proper git commit message for the merge.

I think it might be better to squash commits from pull requests somehow instead and only validate commit messages that we ourselves write, to not make it an overly arduous process for external contribs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great idea. Just pushed a change to (hopefully) enable this:

  # For PRs, validate the PR title (which becomes the squash commit message).
  # This avoids burdening external contributors with conventional commit enforcement
  # on every individual commit. See: https://www.conventionalcommits.org/en/v1.0.0/
  #
  # For pushes to main, validate the final commit message (the squash merge commit)
  # using commitizen as a safety net.
  commitlint:
    runs-on: ubuntu-latest
    steps:
      # PR: validate PR title follows conventional commit format
      - name: Validate PR title
        if: github.event_name == 'pull_request'
        uses: amannn/action-semantic-pull-request@v5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          # For work-in-progress PRs you can typically use draft pull requests from GitHub. 
          # This action allows us to use the special "[WIP]" prefix to indicate a draft state 
          # without actually flagging the PR as a draft.
          # Example:
          # `[WIP] feat: Add support for Node.js 18` <--- will not be validated!
          wip: true

      # Push to main: validate the squash merge commit message directly
      - name: Checkout
        if: github.event_name == 'push'
        uses: actions/checkout@v6
        with:
          fetch-depth: 2

      - name: Install uv
        if: github.event_name == 'push'
        uses: astral-sh/setup-uv@v7
        with:
          version: "latest"

      - name: Install Python and dependencies
        if: github.event_name == 'push'
        run: |
          uv python install 3.11
          uv sync --all-extras

      - name: Validate commit message
        if: github.event_name == 'push'
        run: uv run cz check --rev-range HEAD~1..HEAD

The first step uses an off the shelf GH action, amannn/action-semantic-pull-request@v5 to ensure that the pull request title match the Conventional Commits spec. It only runs on pull_request events.

The latter four steps run on push events, and will check the final/actual commit for pushes to main as a safety measure.

We'll probably want to configure the repo to only allow squash merges (or at least default to them):

  • Settings --> General --> Pull Requests → check only "Allow squash merging" and set the default commit message to "Pull request title".

This will ensure the conventional PR title always becomes the merge commit message that PSR parses.

(We'll of course need to test this.)

- pip install poetry
post_install:
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs
commands:
Copy link
Contributor

Choose a reason for hiding this comment

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

according to the readthedocs docs, we should lean towards using build.jobs stuff instead

https://docs.readthedocs.com/platform/stable/config-file/v2.html#build-commands

we recommend using build.jobs instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I implemented build.jobs correctly but it's possible I assigned the commands to the incorrect build stages.

https://docs.readthedocs.com/platform/stable/builds.html

https://docs.readthedocs.com/platform/stable/build-customization.html

@sonarqubecloud
Copy link

Copy link
Contributor

@jondricek jondricek left a comment

Choose a reason for hiding this comment

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

Oh, interesting - it didn't share these comments yet

seansica and others added 11 commits February 13, 2026 14:10
Co-authored-by: Jared Ondricek <90368810+jondricek@users.noreply.github.com>
Co-authored-by: Jared Ondricek <90368810+jondricek@users.noreply.github.com>
Co-authored-by: Jared Ondricek <90368810+jondricek@users.noreply.github.com>
- Still checks the actual commit for pushes to main
- Uses amannn/action-semantic-pull-request action

Will need to configure the repo to only allow squash merges
Co-authored-by: Jared Ondricek <90368810+jondricek@users.noreply.github.com>
Co-authored-by: Jared Ondricek <90368810+jondricek@users.noreply.github.com>
seansica and others added 5 commits February 13, 2026 15:12
This is needed by amann/action-semantic-pull-request to validate
the PR title against Conventional Commit syntax rules.
- pr-title.yml runs on pull_request_target and checks the PR title
- ci.yml::commitlint runs on push to main only and expects commits to be squashed
- ci.yml::[lint,test] run on push + pull_request for all
@sonarqubecloud
Copy link

@jondricek jondricek merged commit a927cef into main Feb 13, 2026
7 checks passed
@jondricek jondricek deleted the semantic-release branch February 13, 2026 21:57
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.

2 participants