Release Process¶
kibana-py releases are automated. Pushing a version tag (vX.Y.Z) to GitHub runs
the release workflow, which validates the tag, builds the distribution, creates the
GitHub Release, and publishes to PyPI. You do not run twine upload or create the
GitHub Release by hand — doing so collides with the workflow.
The canonical definition of the pipeline is
.github/workflows/release.yml;
this page explains how to drive it.
How a release runs¶
A single action — pushing an annotated vX.Y.Z tag whose commit is on main — triggers
the whole pipeline:
flowchart TD
push(["git push origin vX.Y.Z"]) --> validate
validate["<b>validate-release</b><br/>tag reachable from main?<br/>tag == kibana/_version.py?<br/>CHANGELOG entry exists?"]
validate -- "any check fails" --> halt(["release stops — nothing published"])
validate -- "all pass" --> build["<b>build</b><br/>python -m build → twine check<br/>wheel-content guard → SBOM<br/>upload dist artifact"]
build --> ghrel["<b>publish-github-release</b><br/>generated notes + dist/* attached"]
build --> pypi["<b>publish-pypi</b><br/>OIDC trusted publishing — no token"]
ghrel --> pypi
Jobs, in order (source of truth: release.yml):
Job |
Runs after |
Permissions |
What it does |
|---|---|---|---|
|
tag push |
|
Fails the release unless: the tagged commit is reachable from |
|
|
|
Installs |
|
|
|
Downloads |
|
|
|
Downloads |
One-time setup¶
These are configured once for the project, not per release.
PyPI trusted publishing (OIDC)¶
publish-pypi authenticates to PyPI with a short-lived OIDC token (id-token: write),
so no PyPI API token or ~/.pypirc is involved in a release. This requires a
one-time trusted publisher registered on PyPI for the project:
Project:
kibana-pyOwner / repository:
pedro-angel/kibana-pyWorkflow filename:
release.ymlEnvironment: none (the
publish-pypijob inrelease.ymldeclares no environment)
Register it under PyPI → the project → Settings → Publishing (for a brand-new project, add a pending publisher first). See the PyPI trusted publishing guide.
Note
If you want a deployment-gate (manual approval before publish), add an
environment: release line to the publish-pypi job in release.yml, create a
GitHub Environment named release with the desired protection rules, and add that
environment name to the trusted publisher on PyPI.
Read the Docs¶
Documentation is built by Read the Docs from
.readthedocs.yaml
(Ubuntu 24.04, Python 3.14, install .[docs], fail_on_warning: true, builds HTML +
PDF + ePub). RTD builds on each push/tag via its webhook. latest tracks main;
stable tracks the highest non-prerelease tag.
One-time setup is an RTD project
imported from the GitHub repo
by a maintainer with admin access to the RTD account that owns kibana-py. After the
first tag, confirm in the RTD dashboard that the new version built and is activated, and
that stable points at it.
Local tools¶
For local checks you only need the dev environment:
make setup PYTHON=python3.11 # requires-python is >=3.11
Watching the release run also uses the gh CLI
(gh auth login with repo access) — optional; the GitHub Actions tab works too.
A PyPI/TestPyPI API token is needed only for the optional manual dry-run (below) — never for an automated release.
Pre-release checklist¶
[ ]
make checkpasses (hooks, lint, dependency audit, SAST, unit tests, docs)[ ]
make test-python-matrixpasses (multi-Python unit matrix via nox; fails closed if any supported interpreter is missing — install them via pyenv)[ ]
make test-integrationpasses locally against a live stack — required; CI does not run it (needs a Docker Elastic Stack)[ ] Documentation builds clean:
make docs(already covered bymake check) (HTML with-W+ linkcheck, matching RTD’sfail_on_warning)[ ] Version bumped in
kibana/_version.py[ ]
CHANGELOG.mdupdated (entry and reference links)
Step by step¶
Set a shell variable for the version; it is used from Step 4 onward to keep the tag and verification commands consistent:
VERSION=X.Y.Z # the version you are releasing
1. Bump the version¶
The version lives in one place — kibana/_version.py:
# kibana/_version.py
__versionstr__ = "X.Y.Z"
pyproject.toml declares dynamic = ["version"] and reads this file via
[tool.hatch.version], so do not edit a version in pyproject.toml — there isn’t
one. validate-release reads the same file, so the tag must equal v$VERSION.
2. Update the changelog¶
Add a dated entry (the workflow checks the ## [X.Y.Z] heading exists):
## [X.Y.Z] - YYYY-MM-DD
### Added
- ...
### Fixed
- ...
Also update the reference-link footer at the bottom of CHANGELOG.md: add the
[X.Y.Z] compare/tag link and re-base the [Unreleased] compare range on vX.Y.Z.
3. Land the release commit on main¶
validate-release runs git merge-base --is-ancestor <tagged-commit> origin/main and
fails the release if the tag is not on main. Land the version + changelog change on
main first through the normal
contribution workflow
(branch → PR → merge). Then check out main and confirm the commit you are about to tag:
git checkout main && git pull
git log -1 --oneline # confirm this is your version-bump + changelog commit
4. Tag and push — this publishes¶
git tag -a "v$VERSION" -m "Release $VERSION"
git push origin "v$VERSION"
That tag push is the release. Watch it run and confirm every job is green — with the
gh CLI (authenticated), or from the repository’s
Actions → Release tab:
gh run watch "$(gh run list --workflow release.yml --limit 1 --json databaseId --jq '.[0].databaseId')"
5. Verify¶
PyPI: the package page shows the new version, and a clean install works:
python3.14 -m venv /tmp/verify && /tmp/verify/bin/pip install "kibana-py==$VERSION" /tmp/verify/bin/python -c "from kibana import Kibana, AsyncKibana; print('ok')" rm -rf /tmp/verify
GitHub Release: a release for
vX.Y.Zexists with generated notes and the wheel, sdist, andsbom.cdx.jsonattached.Read the Docs: the tagged version built successfully and is activated; the version selector shows it and
stablepoints at it.
If a job fails¶
Symptom |
Cause |
Fix |
|---|---|---|
|
Tag is on a branch not merged to |
Merge to |
|
Tag ≠ |
Fix |
|
No |
Add the changelog entry, re-tag. |
|
sdist/wheel packaging changed |
Check |
|
Trusted publisher not registered |
Complete the PyPI trusted publisher setup. |
Recovering a failed run. If a job fails before the PyPI upload, fix the cause and
re-run the failed jobs from the Actions tab — or delete and re-push the tag
(git push --delete origin "v$VERSION" then re-tag main), which re-runs the pipeline
from validate-release. Re-running publish-github-release is safe: it updates the
existing release for the tag rather than erroring. Re-running publish-pypi succeeds
only if that version was never uploaded — PyPI refuses to replace an existing version, so
if it already published you must bump to a new version.
| publish-pypi: “File already exists” | This version was already uploaded (e.g. a manual twine upload) | You cannot re-publish a version. Bump to a new version; never manually upload a version you intend to release via the workflow. |
Optional: local dry-run to TestPyPI¶
Warning
This is an optional local sanity check, not a release step. Production publishing is automated (see How a release runs). Nothing here is required to ship a release.
To exercise packaging end-to-end before tagging, build locally and upload to TestPyPI
using a TestPyPI API token in ~/.pypirc. The --repository testpypi flag is what
keeps this off production PyPI — without it, twine upload targets real PyPI:
rm -rf dist/ # avoid uploading stale builds
make build # python -m build + twine check
twine upload --repository testpypi dist/*
python3.14 -m venv /tmp/testpypi \
&& /tmp/testpypi/bin/pip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ kibana-py
rm -rf /tmp/testpypi
Appendix: emergency manual publish (fallback only)¶
Warning
Use this only if automated OIDC publishing is unavailable. Do not run it and also push the tag — the workflow would try to publish the same version and fail with “File already exists”. Pick one path.
With a PyPI API token in ~/.pypirc:
rm -rf dist/
make build
twine upload dist/* # NOTE: no --repository flag = production PyPI
Then create the GitHub Release and tag by hand to match. Prefer fixing the automated path over relying on this.
References¶
.github/workflows/release.yml— the pipeline (source of truth)