Skip to content

Latest commit

 

History

History
154 lines (118 loc) · 5.63 KB

python-packages.md

File metadata and controls

154 lines (118 loc) · 5.63 KB

Releasing a Python package

The order below is the one the commands assume. Each step produces a result the next one depends on, so a skipped step is a missing result rather than a shortcut.

The project needs a release declaration; see The release declaration. Add releasing to a development dependency group, then run the commands with uv run release ....

Table of contents

Before the release

Run the project's own gate first: formatting, linting, type checks, the test matrix and whatever else the project verifies. release does not replace it and knows nothing about it.

Decide the version according to Semantic Versioning.

The release

For a workspace, replace step 7 with the per-distribution commands under Workspaces.

version="<major.minor.patch>"

# 1. Move the version and the changelog to the new release.
uv run release version bump "${version}"
uv run release changelog release "${version}"

# 2. Review and commit. The tag will name this commit.
git diff
git add --all
git commit -m "release: prepare ${version}"
git status --short

# 3. Build from the committed revision. This prepares the README inside the
#    export, so the working tree keeps its relative links.
uv run release build --out "../dist-${version}" --expect "${version}"

# 4. Tag the revision that was built, then publish branch and tag together.
uv run release tag create "${version}" \
  --manifest "../dist-${version}/artifacts.json"
uv run release push "${version}"

# 5. Publish exactly the files that were validated.
printf 'PyPI API token: '
read -rs UV_PUBLISH_TOKEN
printf '\n'
export UV_PUBLISH_TOKEN
uv run release publish "../dist-${version}/artifacts.json"
unset UV_PUBLISH_TOKEN

# 6. Create the forge release entry from the changelog and the manifest.
uv run release forge release-create "${version}" \
  --manifest "../dist-${version}/artifacts.json"

# 7. Verify what the index and the forge now serve.
uv run release verify "../dist-${version}/artifacts.json"

# At any point, ask where the release stands.
uv run release status "${version}" --manifest "../dist-${version}/artifacts.json"

Why this order

The build runs after the commit and before the tag. Building from the commit means the artifacts cannot contain an uncommitted file, and the README preparation happens inside the export, so the committed README keeps the relative links that work on the forge.

Tagging after the build means a build that fails leaves no tag to delete. Passing the manifest to tag create closes the window between the two: a commit made in it leaves the tree clean and the version sites correct, so only the manifest's source revision can tell that the tag would name something no artifact came from.

Before any artifact is uploaded and while no forge release exists, release tag delete can remove a tag that named the wrong revision. Once an artifact is uploaded or a forge release exists, correcting the published content needs a new version.

A version can be uploaded to PyPI only once. A broken release cannot be replaced, only yanked, so step 5 uploads the files whose digests the build recorded and step 7 checks that the index serves those same bytes.

Workspaces

A repository releasing several packages in lockstep declares every member's version file, the requirement whose lower bound follows the version, and the member READMEs the prepared project README is copied over:

[tool.releasing]
repository = "foundata/example"
version-files = [
  "packages/example/pyproject.toml",
  "packages/example-gui/pyproject.toml",
]
dependency-pins = [
  { file = "packages/example-gui/pyproject.toml", name = "example" },
]

[[tool.releasing.readmes]]
copies = ["packages/example/README.md", "packages/example-gui/README.md"]

version bump then raises the pin with the version, and version check fails when a member falls behind. A new major version still needs the upper bound of the pin raised by hand.

In step 7, verify each distribution separately. A manifest with several distributions requires --distribution; the command checks only the selected package:

uv run release verify "../dist-${version}/artifacts.json" --distribution example
uv run release verify "../dist-${version}/artifacts.json" --distribution example-gui

If something goes wrong

Run release status with the version and, if available, the manifest to identify the unfinished or broken steps.

If no artifact has been uploaded and no forge release exists, fix the problem and repeat from the affected step. Source changes need a new commit and build; delete the old build directory first, since build refuses to write into an existing one. If a tag already names the old commit, delete it before tagging the rebuilt revision:

uv run release tag delete "${version}"   # local and remote

If any artifact has been uploaded or a forge release exists, keep the tag and validated artifacts. For an interrupted publication of correct artifacts, resume the missing steps, such as creating the forge release, then run verification. If the published content is wrong, the version is spent: prepare a new patch version instead of rebuilding or retagging that version.