Package naming
PLESTY packages follow a fixed naming convention:
| Convention | Example | |
|---|---|---|
Distribution name (PyPI, pyproject.toml [project] name) |
plesty-<module> |
plesty-power-meter |
| Import package (namespace) | plesty.<module> |
plesty.power_meter |
The distribution name is what users type in pip install / uv add; the import package lives inside the shared plesty namespace. plesty init scaffolds both correctly — verify they still match before the first release, because the distribution name on PyPI cannot be changed later without publishing under a new name.
One-time setup: PyPI credentials
The release job (Gate 14) uploads with uv publish --token "$PYPI_TOKEN". Before the first release of a project, PYPI_TOKEN must be configured — once per project:
- Create the token on PyPI: pypi.org → Account settings → API tokens → Add API token. After the first release exists, replace it with a token scoped to the single project instead of the whole account.
- Add it as a CI/CD variable: in the GitLab project, Settings → CI/CD → Variables → add
PYPI_TOKEN, flagged Masked and Protected. - Protect the release tags: Settings → Repository → Protected tags → protect the pattern
v*.
Step 3 is the one that is easy to miss: a protected variable is only exposed to pipelines running on protected branches or tags. If v* tags are not protected, every gate passes but the release job fails with PYPI_TOKEN is empty. If you see that error on an otherwise green pipeline, check the protected-tags setting first.
For a dry run against TestPyPI (see below), no CI variable is needed — the upload runs locally with a TestPyPI token.
Pre-flight checklist
Before tagging, verify everything is in order:
# 1. Run the full quantum check
uv run plesty check --standard quantum
# 2. Review unreleased commits
git log $(git describe --tags --abbrev=0 2>/dev/null || echo "")..HEAD --oneline
# 3. Confirm CHANGELOG is updated
cat CHANGELOG.md | head -20
# 4. Confirm the current branch is clean
git status
All gates must pass. The CHANGELOG must have an entry for this release. Do not tag until the check passes — CI Gate 14 runs the same check in a clean environment and will reject the build if it fails.
Determine the version bump
| Commits since last tag contain | Bump |
|---|---|
Any feat: or feat!: (no breaking change) |
minor: v0.2.x → v0.3.0 |
Only fix:, chore:, docs:, refactor: |
patch: v0.2.1 → v0.2.2 |
Any feat!: or BREAKING CHANGE: in footer |
major: v0.2.x → v1.0.0 |
Tag and push
git tag v0.3.0
git push origin v0.3.0
Do not push the tag to a branch — push the tag directly. GitLab detects the v* pattern and triggers Gate 14.
What happens in CI after the tag push
- check stage:
plesty check --standard quantumruns in a clean environment - security stage: secret scan
- deploy stage: Sphinx docs build → push to
docs-buildbranch - release stage:
uv build→uv publish --token "$PYPI_TOKEN"to PyPI
The package is live on PyPI within a few minutes of the pipeline completing. It will be installable as:
pip install plesty-power-meter==0.3.0
# or
uv add plesty-power-meter==0.3.0
Monitoring the pipeline
glab ci list -R plesty/hub/devices/<vendor>/<project> --ref v0.3.0
Or watch on the GitLab web UI under CI/CD → Pipelines.
Dry run: the TestPyPI channel
Before a first real release, verify the packaging end-to-end on TestPyPI. Build locally and upload with a TestPyPI token:
uv build
uv run twine upload --repository testpypi dist/*
Then install from the test channel in a clean environment (--extra-index-url lets dependencies resolve from real PyPI):
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
<your-package>
Runnable end-to-end example
The demo experiment (plesty-demo-experiment) is published on the TestPyPI channel and runs a mock line scan with no hardware attached — it is the reference for what a finished, published module looks like:
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
plesty-demo-experiment
python -m plesty.demo_experiment --points 5
After the release
- Update
docs/index.mdto reference the new version (if it mentions a version number) - Start the next CHANGELOG section:
## Unreleased
(no changes yet)
- Notify the team if this release has API changes that downstream experiments must accommodate