Tiny, local-first release tools for modern Python projects.
fastship gives you the same workflow feel as the nbdev nbdev-bump-version, release-pypi, and release-gh commands — but for plain (non-notebook) Python projects.
pip install fastshipCreate a new project:
ship-new my-project
cd my-project
pip install -e .[dev]This creates a complete project with pyproject.toml, __version__, LICENSE, README, and everything wired for fastship.
Bump a version part (0=major, 1=minor, 2=patch). For Rust projects (a Cargo.toml next to pyproject.toml) it bumps [package].version in Cargo.toml and runs maturin develop; otherwise it rewrites __version__ in your package __init__.py:
ship-bump --part 2
ship-bump --part 1
ship-bump --part 0Decrement instead:
ship-bump --part 2 --unbumpBuild + upload to PyPI:
ship-pypiUpload to a named repository in ~/.pypirc (e.g. testpypi):
ship-pypi --repository testpypiQuiet mode:
ship-pypi --quietFastship can also handle the repeated local tooling for PyO3 projects that use maturin and need Rust CLI binaries bundled into wheel scripts.
Create a new PyO3 project:
ship-rs-new my-project
cd my-project
pip install -e .[dev]
ship-rs-testUse Cargo.toml as the version source:
[project]
name = "my_project"
dynamic = ["version"]Commands:
ship-rs-new my-project # create a new maturin/PyO3 project
ship-rs-init # configure an existing maturin/PyO3 project
ship-rs-build # maturin build --release -o dist
ship-rs-test # build/install wheel, pytest -q
ship-bump # bump Cargo.toml version, then refresh the local editable install
ship-rs-release # tag v<version> and push branch + tagsship-rs-init must be run from an existing maturin project with Cargo.toml. It sets [project].dynamic = ["version"], removes [project].version, and exposes __version__ from CARGO_PKG_VERSION when it finds the PyO3 module.
Generated CI runs the tests, then builds wheels with maturin-action across an OS matrix (manylinux: auto on Linux) and publishes to GitHub Releases and PyPI on v* tags. Any CLI tools are Python console scripts declared in [project.scripts]; there are no native Rust binaries to build.
Create a PR from uncommitted or unpushed work, merge it immediately, and clean up:
ship-pr "Add new feature"
ship-pr "Fix bug" --label bug
ship-pr "Breaking change" --label breakingThis command:
- Creates a new branch from your current work
- Commits any uncommitted changes (using the title as commit message)
- Pushes to origin and creates a PR
- Adds the specified label (default:
enhancement) - Squash-merges the PR
- Deletes the remote branch and resets local to updated main
You must be on the default branch (usually main) with no unpulled changes.
Generate or update CHANGELOG.md from closed GitHub issues since your last release:
ship-changelogThis is useful when you want to edit the changelog separately (e.g., in an editor or Claude Code) before releasing.
If you already have a CHANGELOG.md, it must include <!-- do not remove --> near the top so fastship knows where to insert the next release notes.
This is an interactive helper:
- Creates/updates
CHANGELOG.mdfrom closed GitHub issues since your last GitHub release - Opens your
$EDITOR(defaults tonano) so you can edit the changelog - Prompts you to confirm
- Runs
git commit -am release,git push - Creates a GitHub release tagged with your current
__version__
ship-ghIf you've already prepared the changelog (e.g., via ship-changelog), skip the changelog step:
ship-gh --no_changelogThis still opens CHANGELOG.md in your editor for final review before the release is created.
ship-gh looks for a token in this order:
FASTSHIP_TOKEN- a
./tokenfile in your repo root GITHUB_TOKEN
The token must have permission to create releases (typically repo scope for classic PATs, or appropriate fine-grained permissions).
Full release workflow assuming changelog is ready:
ship-changelog # generate changelog, edit as needed
ship-release # release to GitHub + PyPI, bump version, pushThis runs:
ship-gh --no_changelog(openCHANGELOG.mdfor final review, commit if needed, push, create GitHub release)ship-pypi(upload to PyPI)ship-bump(bump patch version)- Commit and push the version bump
ship-pypidoes not bump your version for you — keep it explicit and boring.ship-ghrequires that your project has a gitoriginremote pointing at GitHub (or use--repo OWNER/REPO).
To add fastship to an existing project:
In your package's main __init__.py:
__version__ = "0.0.1"[project]
name = "my-project"
dynamic = ["version"]
[tool.setuptools.dynamic]
version = { attr = "my_project.__version__" }Keep __version__ = "x.y.z" as a simple literal (don't compute it). ship-bump will rewrite this line near the top of the file to keep builds happy.
Fastship infers your package name from [project].name (changing - to _). To override the release branch:
[tool.fastship]
branch = "main" # defaults to current git branch