pyrls
Automated Python release tooling for Git repositories — a single binary that handles version bumps, changelogs, release PRs, GitHub Releases, and PyPI publishing.
Features
- Conventional Commits — derives version bumps from commit messages (
fix:→ patch,feat:→ minor,feat!:→ major) - PEP 440 versions — full support for standard, pre-release (
a,b,rc), post-release, and dev versions - Changelog generation — auto-generates
CHANGELOG.mdin Keep a Changelog format - Release PRs — opens and maintains a PR that accumulates changes; release happens when you merge it
- GitHub Releases — creates git tags and GitHub Releases with changelog notes on PR merge
- PyPI publishing — optional integration with
uv publishortwine upload, including OIDC Trusted Publisher support - Monorepo support — independent versioning and release PRs for multiple packages in one repo
- Single binary — written in Rust, no runtime dependencies
Installation
From GitHub Releases
Download the latest binary for your platform:
# Linux (x86_64)
From source
Or build directly:
# Binary at ./target/release/pyrls
Quick Start
# 1. Initialize config in your Python project
# 2. Make some commits using Conventional Commits format
# 3. Check what pyrls would do
# 4. Create a release PR on GitHub
Configuration
All configuration lives in pyrls.toml at the repo root. Running pyrls init auto-detects your project layout and generates a starting config.
# ── Release settings ─────────────────────────────────────────────
[]
= "main" # branch to watch for new commits
= "v" # tag format: v1.2.3
= "CHANGELOG.md" # path to changelog file
= "chore(release): {version}" # release PR title template
# ── Versioning ───────────────────────────────────────────────────
[]
= "conventional_commits" # only supported strategy for now
= "0.1.0" # version to use if no tags exist
# ── Version files ────────────────────────────────────────────────
# Where to read and write the version string.
# Each entry needs either `key` (for structured files) or `pattern` (for text files).
[[]]
= "pyproject.toml"
= "project.version" # dotted key into the TOML structure
[[]]
= "src/mypackage/__init__.py"
= '__version__ = "{version}"' # {version} is replaced with the actual version
[[]]
= "setup.cfg"
= "metadata.version"
# ── Changelog ────────────────────────────────────────────────────
# Map commit types to changelog sections.
# Set to false to exclude a commit type from the changelog entirely.
[]
= "Added"
= "Fixed"
= "Changed"
= "Changed"
= false # excluded from changelog
# ── Publishing (opt-in) ─────────────────────────────────────────
[]
= false # publishing is never on by default
= "uv" # "uv" or "twine"
= "pypi" # repository name or custom URL
# repository_url = "https://..." # optional: explicit index URL
= "dist" # directory containing built distributions
= false # enable OIDC Trusted Publisher (no token needed)
# username_env = "PYPI_USERNAME" # env var for username (optional)
# password_env = "PYPI_PASSWORD" # env var for password (optional)
# token_env = "PYPI_TOKEN" # env var for API token (optional)
# ── GitHub ───────────────────────────────────────────────────────
[]
# owner = "myorg" # auto-detected from git remote
# repo = "myproject" # auto-detected from git remote
= "https://api.github.com" # override for GitHub Enterprise
= "GITHUB_TOKEN" # env var to read the token from
= "pyrls/release" # branch name prefix for release PRs
= "autorelease: pending" # label applied to open release PRs
= "autorelease: tagged" # label applied after tagging
# ── Monorepo ─────────────────────────────────────────────────────
[]
= false # set to true for multi-package repos
= [] # list of package directories
= "unified" # "unified" (one PR) or "per_package" (one PR each)
CLI Reference
Global flags
--config <PATH> Path to config file (default: pyrls.toml)
--dry-run Print what would happen without making changes
--verbose Enable debug output
--no-color Disable ANSI colour output
Commands
pyrls init
Generate a pyrls.toml config file by auto-detecting your project layout. Detects pyproject.toml, setup.cfg, and __version__ patterns in Python files. Fails if a config file already exists.
pyrls status
Analyze commits since the last release and display a summary: current version, proposed bump, next version, pending changelog entries, and package plan details.
pyrls validate
Parse and validate the config file. Reports the release branch and number of configured version files.
pyrls release pr
Create or update the release PR on GitHub. The PR includes the proposed changelog entry, version bump, and is labeled autorelease: pending. In monorepo mode, creates one PR per package or a unified PR depending on config.
pyrls release tag
Create a git tag and GitHub Release with the changelog section as release notes. Typically called by CI after the release PR is merged. Labels the merged PR with autorelease: tagged.
pyrls release publish
Publish distributions to PyPI (or a custom index) using the configured provider (uv or twine). Requires [publish] enabled = true in config.
GitHub Actions
The recommended workflow uses the pyrls/action wrapper, which downloads the correct binary for your runner — no Rust or Node runtime needed.
# .github/workflows/release.yml
name: Release
on:
push:
branches:
permissions:
contents: write
pull-requests: write
id-token: write # for OIDC PyPI publishing
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pyrls/action@v1
with:
command: release pr
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish:
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
- uses: pyrls/action@v1
with:
command: release publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
How It Works
pyrls follows the Release PR model:
- Scan — on every push to main, pyrls analyzes new commits since the last release tag
- Accumulate — it opens (or updates) a Release PR containing the proposed version bump and changelog entry
- Release — when a maintainer merges the PR, CI calls
pyrls release tagto create the git tag and GitHub Release - Publish — optionally, CI calls
pyrls release publishto push distributions to PyPI
This gives maintainers human-in-the-loop control — releases only happen when you merge the PR.
Conventional Commits
Version bumps are derived from commit messages:
| Commit type | Version bump | Example |
|---|---|---|
fix: |
Patch | 1.0.0 → 1.0.1 |
feat: |
Minor | 1.0.0 → 1.1.0 |
feat!: or BREAKING CHANGE: |
Major | 1.0.0 → 2.0.0 |
Pre-release Versions
pyrls supports PEP 440 pre-release versions:
- Alpha:
1.2.0a1 - Beta:
1.2.0b1 - Release candidate:
1.2.0rc1 - Post-release:
1.2.0.post1 - Dev:
1.2.0.dev1
Pre-release and finalization support is planned via --pre-release and --finalize flags on the release commands (see the PRD for roadmap details).
Monorepo Support
Enable monorepo mode to manage multiple Python packages in a single repository with independent versioning.
# pyrls.toml
[]
= true
= [
"packages/core",
"packages/cli",
"packages/sdk",
]
= "per_package" # or "unified"
per_package— one release PR per changed packageunified— one PR covering all changed packages
Each package directory should contain its own pyproject.toml. pyrls detects which packages have changed and creates version bumps independently.
When monorepo mode is enabled, the [[version_files]] requirement at the top level is relaxed — version files are resolved per package instead.
License
MIT