How a new version of clevercloud-sdk reaches PyPI. Publishing is manual and
run from a maintainer's machine: there is no publishing workflow and no PyPI
token stored in the repository.
A published version is permanent. PyPI does not allow reusing a version number, even after deleting a release — a mistake can only be yanked, which hides it from resolvers without freeing the number. Everything below is ordered so the irreversible step comes last.
- Maintainer rights on the
clevercloud-sdkPyPI project. - A PyPI API token, created under Account settings → API tokens, scoped to this project rather than to the whole account.
uvinstalled locally.
Pick the number according to semantic versioning: a
breaking change to the public API means a new major (or minor while 0.x).
Update it in both places — they are not derived from one another, and a mismatch is only visible after publication:
versioninpyproject.toml__version__insrc/clever_cloud/__init__.py
Then add the release section to CHANGELOG.md, keeping the existing headings:
Security, Fixed, Added, Breaking changes. Anything that forces users to touch
their code belongs under Breaking changes, with the migration in one line.
uv sync --extra dev
uv run ruff check .
uv run mypy
uv run pytest --covAll three must pass. CI runs the same on Python 3.11, 3.12 and 3.13; run it locally against the oldest supported version if you touched anything typing- or syntax-related:
uv run --python 3.11 --extra dev --isolated pytestLand the changes on main through a pull request, then tag the merge commit
so the tag points at reviewed code:
git checkout main && git pull
git tag -a v0.2.0 -m "v0.2.0
<short summary, one line per significant change>"
git push origin v0.2.0Tags are named vMAJOR.MINOR.PATCH. Annotated (-a), not lightweight, so the
tag carries an author, a date and a message.
rm -rf dist
uv build
uvx twine check dist/*twine check validates the metadata PyPI will reject at upload time. Also
confirm the wheel ships the typing marker — the package advertises
Typing :: Typed, and without this file type checkers ignore the annotations
entirely:
python -m zipfile -l dist/*.whl | grep clever_cloud/py.typedOptionally, install the built artifact in a throwaway environment and import it, which catches a broken package that still builds fine:
uv run --isolated --no-project --with dist/*.whl python -c "
import clever_cloud; print(clever_cloud.__version__)"Test it against TestPyPI first if anything about the packaging changed:
uv publish --publish-url https://test.pypi.org/legacy/ --token <test-token>Then publish for real. This is the irreversible step:
uv publish --token <pypi-token>
# or: UV_PUBLISH_TOKEN=<pypi-token> uv publishPass the token on the command line or through UV_PUBLISH_TOKEN; do not write
it into a file in the repository.
Verify the result:
uv run --isolated --no-project --with clevercloud-sdk==0.2.0 python -c "
import clever_cloud; print(clever_cloud.__version__)"Publish the release notes from the CHANGELOG section for this version, and attach the artifacts you just uploaded so both distributions are archived outside PyPI:
gh release create v0.2.0 \
--title "v0.2.0 — <headline>" \
--notes-file <notes.md> \
dist/clevercloud_sdk-0.2.0-py3-none-any.whl \
dist/clevercloud_sdk-0.2.0.tar.gzA bad version cannot be replaced. Yank it, then publish a fixed one:
# On pypi.org: project → Manage → Releases → Options → YankYanking keeps the files available for anyone who pinned that exact version, but stops resolvers from picking it up. Then bump to the next patch version and go through this document again — never try to reupload the same number.