The Python protocol SDK ships from the private monorepo to the public
mirror at KashDAO/protocol-sdk-python and from there to PyPI. This
document is the runbook.
Audience: maintainers inside the Kash monorepo. The mirror repo has its own
CONTRIBUTING.mdfor external contributors.
- Canonical source: this package, at
packages/protocol-sdk-python/inside the private Kash monorepo. All development happens here. - Public mirror:
github.com/KashDAO/protocol-sdk-pythonis a one-way mirror, synced viascripts/sync-to-public-mirror.ts. - PyPI artifact: published as
kashdao-protocol-sdkeither via the public mirror's GitHub Actions workflow (OIDC trusted publishing) or manually viascripts/publish.sh.
This is the same model @kashdao/protocol-sdk (TypeScript) uses, and
the two SDKs release independently. The Python SDK has no runtime
dependency on the TypeScript SDK; version numbers are not tied
together. Bump and publish each on its own cadence.
Normal monorepo PR workflow against packages/protocol-sdk-python/.
CI runs:
python scripts/sync-abis.py --check— confirms vendored ABIs matchpackages/protocol-sdk/src/shared/contracts/generated/(drift gate).ruff check kashdao_protocol_sdk testsruff format --check kashdao_protocol_sdk testsmypy kashdao_protocol_sdkpytest -m 'not integration and not e2e' -ra --strict-markers
See .github/workflows/protocol-sdk-python-ci.yml.
Edit kashdao_protocol_sdk/_version.py (__version__ = "X.Y.Z").
Update the CHANGELOG: rename [Unreleased] to the new version + date,
leave a fresh empty [Unreleased] heading on top.
Land on monorepo main.
Independence from
@kashdao/protocol-sdk(TS): the TS and Python SDKs version independently. Don't bump the TS SDK just because Python is changing.
pnpm tsx packages/protocol-sdk-python/scripts/sync-to-public-mirror.ts \
--mirror-url=git@github.com:KashDAO/protocol-sdk-python.gitWhat the script does:
- Clones the mirror into a temp dir.
- Wipes its working tree (preserving
.git). - Copies
kashdao_protocol_sdk/,tests/,examples/,scripts/,.github/, README.md, CHANGELOG.md, CONTRIBUTING.md, SECURITY.md, RELEASING.md, LICENSE, HUMMINGBOT_INTEGRATION.md, pyproject.toml, .gitignore. - Drops Python build / cache artefacts (
__pycache__/,.pytest_cache/,.mypy_cache/,.ruff_cache/,.venv/,*.pyc,.egg-info/). - Drops monorepo-only test files (
*.private.test.py). - Strips monorepo-only scripts that have no meaning in the mirror tree:
scripts/sync-to-public-mirror.ts(the mirror has no monorepo to read from) andscripts/sync-abis.py(vendors ABIs from a monorepo path,packages/protocol-sdk/src/shared/contracts/generated/, that only exists inside this monorepo). - Commits with message
release: v<version>and tagsv<version>. - Pushes
main+ the tag to the mirror.
ABI vendor boundary: ABIs are committed in-tree at release time as JSON under
kashdao_protocol_sdk/contracts/abis/. The mirror has nosync-abis.pyand no need for one — the ABIs that ship were already vendored on the monorepo side via the drift-gated CI step (python scripts/sync-abis.py --check). External contributors to the mirror who need to refresh ABIs do so via a monorepo PR; the next mirror sync picks the change up.
Useful flags:
--dry-run— commit + tag locally but don't push.--skip-push— same, more explicit.--local-output=<path>— skip clone entirely; write the prepared mirror tree to a local directory for inspection.
The pyproject.toml is copied verbatim — no workspace-dep rewriting
needed because the package has zero internal monorepo runtime deps.
The normal sync flow commits a fresh release on top of whatever
history the mirror already has. For the first publish you want
the mirror's history to start with a single, clean
initial public release commit — not a trail of internal cleanup.
One-shot recipe:
# Materialise a clean mirror tree locally (no clone, no commit)
pnpm tsx packages/protocol-sdk-python/scripts/sync-to-public-mirror.ts \
--local-output=/tmp/protocol-sdk-python-init
cd /tmp/protocol-sdk-python-init
git init -b main
git add -A
git commit -m "chore: initial public release of kashdao-protocol-sdk"
git remote add origin git@github.com:KashDAO/protocol-sdk-python.git
# Mirror has no external consumers yet — force-push is safe here
# and only here. Every subsequent release goes through the normal
# sync-to-public-mirror.ts flow (clone + copy + commit + tag + push).
git push --force origin mainAfter this one-time push, every subsequent release uses the normal
sync-to-public-mirror.ts flow and the mirror history grows linearly
from the initial commit. The PyPI OIDC workflow on the mirror will
then fire on the first tag pushed by a subsequent release.
Preferred: the public mirror's publish-pypi.yml workflow fires on
tag push (step 3 already pushed v<version>) and uses OIDC trusted
publishing — no API token in any repo. Watch the action; once green,
the package is on PyPI.
Fallback (manual): from the monorepo, dry-run first:
bash packages/protocol-sdk-python/scripts/publish.sh --dry-runThe dry-run runs every gate (ruff, mypy, pytest, ABI drift, build,
SBOM via cyclonedx-py environment) plus the CHANGELOG-slice
extraction for the GitHub Release — but stops short of twine upload and gh release create. Use it to verify the pipeline
end-to-end without touching PyPI. Non-interactive; tolerates
"version already published" + "twine not configured".
When the dry-run is clean, run the real publish:
bash packages/protocol-sdk-python/scripts/publish.shThe script:
- Verifies you're authenticated to PyPI (
twineconfig / API token). - Confirms the version isn't already published.
- Re-runs the pre-publish gate (ruff, mypy, pytest, ABI drift).
- Builds wheel + sdist via
python -m build. - Asks for an interactive
yesconfirmation. - Runs
twine upload dist/*.
⚠️ Manual publish does NOT produce a PyPI sigstore attestation — that requires the OIDC workflow path. Prefer the workflow when possible.
pip index versions kashdao-protocol-sdkOr fetch the metadata:
pip download --no-deps -d /tmp kashdao-protocol-sdk==<version>If the OIDC workflow ran, it likely already drafted a release. If not:
- Visit
https://github.com/KashDAO/protocol-sdk-python/releases/new. - Choose the tag (
v0.1.0). - Title: the version (
v0.1.0). - Description: paste the relevant
CHANGELOG.mdsection. - Publish release.
python -m venv /tmp/kashsmoke && source /tmp/kashsmoke/bin/activate
pip install kashdao-protocol-sdk==<version>
python -c "import kashdao_protocol_sdk as k; print(k.__version__)"
# Expect: <version>Same flow, patch version bump (v0.1.1). Since the TS SDK doesn't
depend on this package, no coordinated release is required — the
hotfix ships independently.
The mirror accepts PRs from external contributors. Once merged into
mirror main:
- Cherry-pick the commit into
packages/protocol-sdk-python/in the monorepo. Adjust paths if needed (the mirror has nopackages/protocol-sdk-python/prefix). - Re-run the monorepo CI.
- Land normally.
The next release sync will pick it up.
| Resource | Who needs access |
|---|---|
PyPI kashdao-protocol-sdk project (publish) |
Whoever runs publish.sh, or the GitHub Actions OIDC role on the mirror |
KashDAO/protocol-sdk-python repo (push) |
Whoever runs the sync script |
If a published version is broken:
- Yank it on PyPI:
pipwill refuse to install yanked versions for new resolves but existing pinned installs continue working. Use the PyPI web UI ortwine yank. - Ship a patch release.
PyPI does not support deletion after upload (mirrors npm's no-unpublish policy). Use yanking + patch.
Candidates for automation on the mirror:
- PyPI trusted publishing via OIDC (already preferred path; ensure workflow is present on every mirror).
- Multi-Python smoke test matrix (3.10 / 3.11 / 3.12).
- Sphinx → GitHub Pages for the API reference.
Deferred until the manual flow has been exercised a few times.