Building and Releasing
Building and Releasing
A release is a version in pyproject.toml, a wheel (and usually an sdist) from uv build, and a tag that matches. The code that runs after install should print that same version. Do not have three version strings.
Mental model
Version is [project].version (or a dynamic scheme you chose on purpose). Bump it when you publish, not when you save a file.
uv build asks the build backend (hatchling here) for two artifacts:
- sdist — source tree in a tarball (
deskshift-1.2.0.tar.gz) - wheel — installable zip (
deskshift-1.2.0-py3-none-any.whl)
Wheels are what uv pip install / uv add consume from an index. The py3-none-any tags mean “pure Python, any OS.” That is the boring target until you wrap a C library.
Releasing is: tests green, ruff clean, version bumped, uv build, upload to your index, git tag v1.2.0. This chapter shows the files and the build command, not a hosting vendor.
Worked examples
Case 1: Version lives in pyproject.toml
Save as a project:
pyproject.toml:
[project]
name = "deskshift"
version = "1.2.0"
description = "Shift labels for a small desk."
requires-python = ">=3.14"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"deskshift/__init__.py:
# deskshift/__init__.py
__version__ = "1.2.0"
def shift_label(name: str) -> str:
return f"shift {name}"The duplicated "1.2.0" is the thing you will eventually replace with importlib.metadata.version("deskshift") after the package is installed. Until then, one constant next to the function is acceptable if you bump both in the same commit.
Case 2: A complete module that prints the version
Save as show_version.py and run it without installing. This is the same string you put in pyproject.toml.
# show_version.py
__version__ = "1.2.0"
def shift_label(name: str) -> str:
return f"shift {name}"
def main() -> None:
print(__version__)
print(shift_label("night"))
if __name__ == "__main__":
main()Run:
uv run python show_version.pyOutput:
1.2.0
shift night
Case 3: Build commands
From the project directory that contains pyproject.toml:
uv buildYou should see files under dist/:
dist/deskshift-1.2.0.tar.gz
dist/deskshift-1.2.0-py3-none-any.whl
Install the wheel into an isolated check:
uv pip install dist/deskshift-1.2.0-py3-none-any.whl
uv run python -c "import deskshift; print(deskshift.__version__)"Expected output of that -c:
1.2.0
If the wheel name differs, list dist/ and install the file that is there. Do not edit a wheel by hand.
Case 4: Read the installed version
Save as installed_version.py. importlib.metadata.version looks at the installed distribution. In this listing we fall back to a constant so the file still runs when you have not installed deskshift.
# installed_version.py
from importlib.metadata import PackageNotFoundError, version
__version__ = "1.2.0"
def package_version() -> str:
try:
return version("deskshift")
except PackageNotFoundError:
return __version__
def main() -> None:
print(package_version())
if __name__ == "__main__":
main()Run:
uv run python installed_version.pyOutput:
1.2.0
After a real install, the metadata path wins. That is the boring long-term source of truth.
The trap
A comment, a constant, and pyproject.toml that disagree.
Save as wrong_version.py:
# wrong_version.py
# version 1.1.0 — leftover comment
__version__ = "1.2.0"
def main() -> None:
print("package 1.0.0")
print(__version__)
if __name__ == "__main__":
main()Run:
uv run python wrong_version.pyOutput:
package 1.0.0
1.2.0
Three numbers, one process. Support will ask “which version?” and every answer will be true. Print __version__ (or importlib.metadata.version) only. Delete the comment. Bump pyproject.toml in the same change.
The boring rule
- One version per release, in
pyproject.toml, tagged in git asvX.Y.Z. uv buildproduces sdist + wheel. Ship both unless you have a reason not to.- Pure Python wheels (
py3-none-any) until you truly need compiled code. - After install, prefer
importlib.metadata.version("deskshift"). - Do not release from a dirty tree. Tests and
ruff checkfirst.
Try this
- Change
__version__inshow_version.pyto1.2.1and printshift_label("day"). - Add
authors = [{name = "Desk"}]to the Case 1pyproject.toml. - In
installed_version.py, print a prefixdeskshiftbefore the version.