molt
Changeset-driven versioning, changelogs, and publishing for Python packages and monorepos. A
Python-native port of changesets, built on PEP 440
versions and PEP 508 requirements instead of SemVer and npm.
Install
The distribution is molt-release. The command it installs is molt.
uv tool install molt-release
molt --help
pipx install molt-release and pip install molt-release work too, and uvx molt-release --help runs it
without installing anything. Requires Python 3.11 or newer, on Windows, macOS, or Linux.
The release loop
You record the intent of a change while you write it. A separate step turns accumulated intent into releases.
molt init # one time: create .changeset/ and a [tool.molt] block
# ... make a change ...
molt add # record which packages changed, and how much
molt version # bump versions, write changelogs, update the lockfile
git commit -am "Release"
molt publish # build, upload to PyPI, tag
molt add writes a small Markdown file you commit alongside your code:
---
"acme-core": minor
---
Add a --stream flag to the export API for large datasets.
molt version consumes every pending changeset in one pass, takes the highest bump per package,
folds the summaries into each CHANGELOG.md, and deletes the files it consumed. All mutations are
buffered and flushed together, so a mid-run failure never leaves a repository half-versioned.
Monorepo propagation
In a workspace, a changeset for one package cascades to the packages that depend on it. Given
acme-cli requiring acme-core>=1.2.0,<2.0.0, a major bump to acme-core plans this:
$ molt status
Packages to be bumped:
- major
- acme-core 1.2.0 -> 2.0.0
- patch
- acme-cli 0.5.0 -> 0.5.1 (dependency bump)
acme-cli has no changeset of its own. Version 2.0.0 falls outside its constraint, so it needs a
release. Molt gives it the smallest bump that does the job and rewrites the constraint to
>=2.0.0,<3.0.0. A minor bump to 1.3.0 would have released nothing extra, because 1.3.0 still
satisfies the range.
Commands
| Command | What it does |
|---|---|
molt init |
Scaffold config and .changeset/; detect the ecosystem backend and workspace layout. |
molt add |
Record a changeset. Bare molt runs this. |
molt version |
Consume changesets: bump versions, propagate to dependents, rewrite pins, write changelogs, update the lockfile. |
molt status |
Report pending changesets and the projected release, as text or JSON. Doubles as a CI gate. |
molt publish |
Build and upload changed packages to PyPI over OIDC Trusted Publishing, in dependency order. |
molt build |
Build sdists and wheels for the packages a plan will publish. |
molt yank |
Check a released version and print the steps to yank it on PyPI (PEP 592). |
molt git-tag |
Create annotated git tags for released packages. |
--dry-run prints the plan for any mutating command and writes nothing. --non-interactive
resolves every prompt to its documented default, which is what Dependabot, Renovate, and codegen
need. Human-facing output goes to stderr and stdout carries machine-readable payloads only, so
molt status --output json | jq '.releases[].name' needs no filtering.
Full reference: CLI.
Python specifics
Some behavior differs from changesets because Python's packaging rules force a different answer.
- PEP 440 version math, through
packaging. Prereleases are1.0.0rc1,1.0.0a2, or1.0.0.dev3from a fixed set of spellings, and package names compare under PEP 503 normalization. - Prerelease is a flag, not a persistent mode:
molt version --pre rc. There is nopre.jsonbranch state to merge or forget. uv.lockis refreshed whenmolt versionbumps a package. uv workspaces are first-class; other backends sit behind an ecosystem seam.- Snapshots target a separate index, because PyPI versions are immutable and every snapshot would otherwise burn a public version number.
- A bad release is yanked, not unpublished.
molt yankverifies the version, reports whether it is already yanked, and prints the exact steps. PyPI exposes no yank API, so the final click is yours.
Continuous integration
The repository root carries a composite GitHub Action that runs the whole loop. It keeps a "Version Packages" pull request in sync with your pending changesets, then builds, publishes, tags, and creates a GitHub Release per package once that pull request merges.
- uses: giancarlosisasi/molt@<sha>
with:
molt-version: "0.1.0"
publish: molt publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
See CI: GitHub Action for the full workflow,
its inputs, and its outputs. Nothing in the loop is GitHub-specific: the same molt commands drive
it anywhere.
Documentation
- What is Molt? and Why Molt?
- Installation, single-package quickstart, monorepo quickstart
- The release plan and Versioning and PEP 440
- Configuration and every option
- Migrating from changesets
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file molt_release-0.1.0.tar.gz.
File metadata
- Download URL: molt_release-0.1.0.tar.gz
- Upload date:
- Size: 907.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05e8ccb48397b4bd251aa7eb787626b3ef6382998ac6346f19d2b175f6888fb9
|
|
| MD5 |
703ebf186846b8cefc0d79415139b122
|
|
| BLAKE2b-256 |
a2945198b94593ce1c6478defa5c8613c36e1d7b28dfbb66d272e78688460afb
|
File details
Details for the file molt_release-0.1.0-py3-none-any.whl.
File metadata
- Download URL: molt_release-0.1.0-py3-none-any.whl
- Upload date:
- Size: 401.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d5bca513cf15ce950d11047541874db550a63f26c77b39ee0ec51e2eafeef23
|
|
| MD5 |
5b890df5b6b2a87f31844574e78bc07e
|
|
| BLAKE2b-256 |
6720e205e38f3c6a39b1fffda86a9a220d5d268a8dd595a3ff9824e52c54cd2c
|