publishing.md

May 23, 2026 · View on GitHub

Publishing Releases

This is how to publish a Python package to PyPI from GitHub Actions, when using the simple-modern-uv template.

Thanks to the dynamic versioning plugin and the publish.yml workflow, you can simply create tagged releases (using standard format for the tag name, e.g. v0.1.0) on GitHub and the tag will trigger a release build, which then uploads it to PyPI.

First-Time Setup

This part is a little confusing the first time. Here is the simplest way to do it. For the purposes of this example replace OWNER and PROJECT with the right values.

Note: These steps assume you already have a GitHub repo with your code pushed. If you used uvx uvtemplate, it handles repo creation for you. If you're setting up manually, create an empty GitHub repo (no README, no .gitignore, no license — the template already provides these) and push your code to it. See the README for details.

  1. Get a PyPI account at pypi.org and sign in.

  2. Pick a name for the project that isn’t already taken.

    • Go to https://pypi.org/project/PROJECT to see if another project with that name already exits.

    • If needed, update your pyproject.toml with the correct name.

  3. Authorize your repository to publish to PyPI:

    • Go to the publishing settings page.

    • Find “Trusted Publisher Management” and register your GitHub repo as a new “pending” trusted publisher.

    • Enter the project name, repo owner, repo name, and publish.yml as the workflow name. (You can leave the “environment name” field blank.)

  4. Create the first release by pushing a tag:

    • Commit code and make sure CI is green in the Actions tab.

    • Tag and push:

      git tag v0.1.0
      git push origin v0.1.0
      

      (Tag names should start with v.)

    • The publish.yml workflow will run automatically, upload to PyPI, and create the matching GitHub Release.

  5. Confirm it publishes to PyPI

    • Watch for the release workflow in the GitHub Actions tab.

    • If it succeeds, you should see it appear at https://pypi.org/project/PROJECT.

Publishing Subsequent Releases

This project uses a tag-triggered workflow: pushing a v* tag to GitHub builds, uploads to PyPI via trusted publishing, and creates the GitHub Release automatically. An agent (or human) only needs to push a tag.

Follow this checklist for each new release.

Pre-Release Checklist

  1. Verify all changes are committed and pushed:

    git status
    git log origin/main..HEAD  # should be empty if pushed
    
  2. Run linting and tests locally:

    make lint
    make test
    
  3. Confirm CI is passing:

    gh run list --limit 3
    

    Or check the Actions tab on GitHub.

  4. Determine the new version number:

    # Check current/latest version:
    gh release list --limit 1
    

    Use semantic versioning:

    • Patch (e.g., v0.5.8v0.5.9): Bug fixes, minor changes

    • Minor (e.g., v0.5.9v0.6.0): New features, backward-compatible

    • Major (e.g., v0.6.0v1.0.0): Breaking changes

Create the Release

  1. Tag and push:

    NEW_TAG="vX.Y.Z"  # Replace with actual version
    git tag "${NEW_TAG}"
    git push origin "${NEW_TAG}"
    

    This triggers the publish.yml workflow, which:

    • Runs the test suite

    • Builds the wheel and sdist

    • Uploads to PyPI via trusted publishing

    • Creates a GitHub Release with auto-generated notes (release notes are generated by GitHub from PR titles / commit messages since the last release)

  2. Watch the workflow:

    gh run list --workflow=publish.yml --limit 1
    gh run watch  # interactive, follow the latest run
    
  3. (Optional) Curate the release notes:

    The workflow uses GitHub's auto-generated notes by default. To replace them with curated notes (recommended for non-trivial releases), see the Release Notes Format section below and run:

    gh release edit "${NEW_TAG}" --notes-file release-notes.md
    

    Or write notes inline:

    LAST_TAG=$(gh release list --limit 2 --json tagName -q '.[1].tagName')
    gh release edit "${NEW_TAG}" --notes "$(cat <<EOF
    ## What's Changed
    
    [Summarize changes here -- see format guide below]
    
    ### Full Changelog
    
    https://github.com/OWNER/PROJECT/compare/${LAST_TAG}...${NEW_TAG}
    EOF
    )"
    
  4. Verify the release published successfully:

    # Verify on PyPI (may take a minute):
    # https://pypi.org/project/PROJECT
    
    # Verify the GitHub Release exists:
    gh release view "${NEW_TAG}"
    

Release Notes Format

Use this structure for release notes:

## What's Changed

### Bug Fixes

**Short title of fix**

Description of what was fixed and why it matters.

### New Features

**Short title of feature**

Description of the new capability.

### Breaking Changes

**Short title of breaking change**

Description of what changed and how to migrate.

### Full Changelog

https://github.com/OWNER/PROJECT/compare/vPREVIOUS...vNEW

Guidelines:

  • Use ## What's Changed as the top-level heading.

  • Group changes under ### Bug Fixes, ### New Features, ### Breaking Changes, etc. as appropriate.

  • Use **bold** for short titles of individual changes.

  • Include technical details only when helpful for users.

  • Always include the Full Changelog compare link at the end.

  • For small releases, a simple bullet list is acceptable instead of full sections.


This file was built with simple-modern-uv.