⚠️ Warning ⚠️
Simple Releaser is alpha software and probably not yet ready for general use. Expect rough edges, breaking changes, and an occasional gremlin.
Simple Releaser
Simple Releaser is a configurable command-line tool for validating, building, tagging, and publishing Python package releases.
The PyPI distribution is named simple-releaser. Once installed, it can be
invoked as either simple-releaser or release.
Simple Releaser grew out of a direct copy of the release workflow used by the QRtsy Project with significant changes to generalize it to accommodate different release workflows. Simple Releaser is deliberately designed to be simple and is unlikely to grow much beyond this initial vision. If you've got more complex needs, you may wish to consider some alternatives.
Project assumptions
Run Simple Releaser from a directory containing the releaser configuration
(releaser.toml by default). This directory may also be the project root,
or the releaser configuration may point at a separate project directory.
The project being released is expected to have:
- Project configuration, including name and version, in
pyproject.toml. - A changelog file named
CHANGELOG.md, withUnreleasedand version sections. - A Git repo with an
originremote.
When publishing a repository release locally, the project is also expected to have:
- A repository hosted by Codeberg or GitHub.
- Repository host tokens or overrides supplied through environment variables or
a
.envfile beside the releaser configuration when needed.
Installation
A convenient way to install Simple Releaser as a standalone command-line
tool is with pipx:
pipx install simple-releaser
Once installed, Simple Releaser can then be invoked as either:
release
or:
simple-releaser
Commands
The available CLI commands are:
release -h
usage: release [-h] [--dry-run] {init,show-config,clean,validate,build,tag,to_pypi,to_repo,run} ...
Validate, build, archive, and publish Python project releases.
A simple project release policy:
- Validate that release is ready
- Run any project-specific checks
- Build package and archive it
- Create release tag
- Publish package to PyPI
- Publish release and assets to repository host
positional arguments:
{init,show-config,clean,validate,build,tag,to_pypi,to_repo,run}
init initialize Simple Releaser configuration
show-config show resolved configuration and project metadata
clean check if project is a clean git repository
validate validate that a new release is ready
build build and archive the current version
tag create and push the current release tag
to_pypi publish the archived distributions to PyPI
to_repo publish the release to repository host
run run the configured release process
options:
-h, --help show this help message and exit
--dry-run show what would be done without making changes
Dry run
Pass --dry-run before or after a command to perform the normal read-only
preflight and describe the release actions without making changes. For example:
release run --dry-run
release --dry-run tag
Dry-run mode still reads project/repository metadata, checks that the Git working
tree is clean, validates the version and changelog, and inspects existing Git
tags. It does not run configured project commands, create the release archive,
create or push Git tags, publish to PyPI, or call the Codeberg/GitHub release
APIs. Because the build is simulated, later dry-run tasks use descriptive
artifact placeholders rather than requiring _releases/<version> to already
exist.
The read-only show-config, clean, and validate commands behave the same
with or without --dry-run.
Initialization
Run release init from the directory where the releaser configuration should
live:
release init
If that directory contains the pyproject.toml for a project, Simple Releaser
creates a minimal releaser.toml for that project. Otherwise it prompts for
the project directory and then records the given project_path in the generated
configuration. Relative paths are used when possible. An existing releaser
configuration is never overwritten.
release init --dry-run performs the same project discovery but only reports
where the configuration would be created.
Configuration
Simple Releaser requires a configuration file in the current working directory.
By default it is named releaser.toml. To use a different filename, set the
RELEASER_CONFIG environment variable.
When the directory also contains the project's pyproject.toml, no project path
is needed:
tag_prefix = "v"
[commands]
run = "check build tag to_pypi to_repo"
If the project lives elsewhere, project_path is required and must identify a
directory containing pyproject.toml. Relative paths are resolved from the
directory containing the releaser configuration; absolute paths are also
accepted. For example:
project_path = "../my-project"
repo_host = "github"
tag_prefix = "v"
[commands]
run = "check build tag to_pypi to_repo"
Set auto_confirm = true at the top level to allow PyPI publication without an
interactive confirmation prompt, for example in a deliberately unattended
release workflow. It defaults to false; even with automatic confirmation,
Simple Releaser still prints the project/version/artifact summary and PyPI URL
before running the publish command.
Build artifacts are archived under _releases in the same directory as the
releaser config file. If _releases is in the project directory, then
Simple Releaser will automatically add a .gitignore file to exclude it from Git.
In the unlikely event that excluding this folder from Git is not desired,
set gitignore_releases = false at the top level to disable this feature.
Configurable commands
The [commands] section currently supports check, build, publish, and run.
A value may be either a shell-like string or a TOML array of argument strings.
The following forms are equivalent:
check = "uv run pytest"
check = ["uv", "run", "pytest"]
Quoting in a string may be used to preserve spaces within one argument:
check = 'python -c "print(1 + 1)"'
An empty string or empty array disables the task associated with that configured command. For example:
[commands]
check = ""
publish = ""
causes the check and to_pypi tasks to be skipped. The publish configuration
name controls the to_pypi task.
Configuring check
The configured check command is the project's generic handoff to its own
release-readiness policy. Tests, linters, type checks, documentation checks,
project-specific validation, or any other prerequisite can live behind this one
command. Since that policy is project-specific, check is disabled by default
and must be customized for the project:
[commands]
check = ""
Simple Releaser deliberately does not try to model all of those project-specific
tasks itself. A useful pattern is to point check at a project task runner, so
the same check task can be used during development, in CI, and during a
release without duplicating workflow configuration. Command strings are parsed
into arguments but are not executed through a shell; compound shell logic is
therefore also usually cleaner inside a task runner or project script.
See project checks and task runners for a discussion of Taskipy, Poe the Poet, Invoke, Hatch, PDM, Pixi, mise, just, Task, and how they fit this design.
The simple-releaser project uses this pattern itself. Its releaser.toml
configures:
[commands]
check = "hatch run poe check"
Configuring build and publish
By default, Simple Releaser builds with build and publishes with twine:
[commands]
build = "{python} -m build --outdir {output}"
publish = "{python} -m twine upload --skip-existing {wheel} {sdist}"
Build and publish commands may use {python}, which expands to the absolute path
of the Python interpreter running Simple Releaser. This lets an override
deliberately use a tool installed alongside Simple Releaser rather than
whichever python happens to be on PATH.
The default publisher uses Twine's normal authentication mechanisms, including
keyring, .pypirc, and TWINE_* environment variables. For repository-host
settings, Simple Releaser checks the process environment first and then falls back
to the .env file beside the releaser configuration.
The {output} placeholder in build is replaced with the temporary directory
where the build command must write the wheel and source distribution. A
non-empty custom build command must include {output}. Publish commands may use
{release_dir}, {wheel}, and {sdist} to refer to the verified archived
release and its two distribution files. A custom publish command does not have
to use a placeholder when the command already knows how to locate the artifacts
it should publish.
The default build and publish commands work with most standards-compliant build
backends declared in the [build-system] section of a project's pyproject.toml
configuration. Projects that prefer their project-management tool's native build
and publish commands can override the defaults. Typical equivalents are:
# Poetry
build = "poetry build --clean --output {output}"
publish = "poetry publish --skip-existing --dist-dir {release_dir}"
# Hatch
build = "hatch build --clean {output}"
publish = "hatch publish --no-prompt {wheel} {sdist}"
# uv
build = "uv build --no-sources --out-dir {output}"
publish = "uv publish {wheel} {sdist}"
# PDM
build = "pdm build --dest {output}"
publish = "pdm publish --no-build --skip-existing --dest {release_dir}"
Configuring run
The run command is an ordered list of task names. Its default value is:
[commands]
run = "check build tag to_pypi to_repo"
The available run task names are check, clean, validate, build, tag,
to_pypi, and to_repo.
cleanis available for release flows that want an explicit clean-repository check at a particular point, but this is usually unnecessary because release validation already checks the working tree and tasks that require cleanliness enforce it themselves.validatemay also be included explicitly, but again this is usually unnecessary since validation is already automatically done beforerunbegins.
Both of these are included as options for custom release plans where revalidation or additional clean checks might be useful.
Local and CI release strategies
The run sequence also determines where the release process stops.
The default sequence performs the complete release locally:
[commands]
run = "check build tag to_pypi to_repo"
For a project whose remote CI publishes after a release tag is pushed, omit the local publication tasks:
[commands]
run = "check build tag"
Here the local build acts as a packaging preflight. Simple Releaser verifies
that the distributions can be built and archived before tag creates and pushes
the release tag. The remote workflow may then rebuild the tagged source and
publish its own artifacts.
Projects with expensive, platform-specific, or otherwise CI-authoritative builds may omit the local build as well:
[commands]
run = "check tag"
In this form, Simple Releaser runs the project's configured check command and
then creates and pushes the release tag; the remote workflow is responsible for
both building and publishing. This avoids a duplicate local build, but a
packaging failure will not be discovered until after the release tag has been
pushed.
The configured check in these examples remains the project's own quality-check
command, such as its tests, linters, and type checks. It is separate from Simple
Releaser's release validation, which the CLI performs before the run sequence
begins.
Because tag pushes the tag to origin, it can serve directly as the handoff to
a tag-triggered CI workflow. Simple Releaser does not need to know which CI
system handles the tag or how that system builds and publishes the release.
When a release tag already exists locally or on origin, Simple Releaser reuses
it only if it resolves to the current project commit; a conflicting tag aborts
the release instead of being overwritten.
For a concrete GitHub Actions workflow using PyPI Trusted Publishing, see GitHub Actions and PyPI Trusted Publishing.
Repository host
The repository host and owner/repo coordinates are normally derived from the
configured project's Git origin. HTTPS and SSH origins on codeberg.org and
github.com are recognized automatically. tag_prefix controls the text
prepended to the project version when creating and locating Git tags; it defaults
to "v". Set it to an empty string to use bare version tags such as 1.2.3.
For unusual arrangements where the Git origin is not the repository that
receives releases, repo_host may explicitly select codeberg or github; pair
it with the corresponding repository override. Repository host values may be set
as environment variables or in the .env file beside releaser.toml; an exported
environment variable takes precedence over the same .env entry.
To publish to a Codeberg repository, set CODEBERG_TOKEN. Set
CODEBERG_REPOSITORY=owner/repo to override the repository coordinates derived
from the Git origin.
To publish to a GitHub repository, set GITHUB_TOKEN. Set
GITHUB_REPOSITORY=owner/repo to override the derived repository coordinates.
The GitHub token must have permission to create releases and upload release
assets.
Before a non-dry-run run containing to_repo starts its first task, Simple
Releaser verifies that the repository can be resolved and that the corresponding
host token is available.
Development
For development purposes, some project checks and a test runner are defined as Poe tasks:
poe lint
poe format-check
poe typecheck
poe test
# Run all of the above
poe check
The test suite can also be run across the matrix of supported Python versions, which currently includes Python 3.11, 3.12, 3.13, and 3.14, using another Poe task:
poe test-matrix
A separate packaging smoke test creates isolated test environments, install the Simple Releaser wheel and sdist package artifacts, and performs some basic checks to verify that the packages are actually installable. This test can be invoked with another Poe task:
poe package-smoke
Finally, a more thorough check meant for CI runs the linters, type checks, tests across all supported Python versions, and the packaging smoke test. It can also be invoked locally via a Poe task:
poe ci
Release files for simple-releaser 0.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| simple_releaser-0.0.3.tar.gz | 48.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| simple_releaser-0.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.8 kB
Release files / simple_releaser-0.0.3.tar.gz
| Download URL | simple_releaser-0.0.3.tar.gz |
|---|---|
| Size | 48.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
634c2b420e1e46ed0261fc28c277b3ad6da15c56595bd27a304f611ffe345bd2
|
|
BLAKE2b-256 checksum How to use checksums |
cf437abcdc32635a16df85c2bcb57286fd214208f79bb9b2397225cd0527d68a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / simple_releaser-0.0.3-py3-none-any.whl
| Download URL | simple_releaser-0.0.3-py3-none-any.whl |
|---|---|
| Size | 26.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
41808428510b50f46ce2efcddbee9b23827ffbf6df8c43eef31c7b93049e473b
|
|
BLAKE2b-256 checksum How to use checksums |
16e2bca8f0990808cef66c6e397cc96df8f5e05edeb3114077cff2eaee7ebdda
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|