Releasing to PyPI
This page covers how to publish xlm-core and xlm-models to PyPI and refresh release docs.
How to release is the step-by-step procedure. How it works explains the workflows, version sources, and GitHub Actions behavior for maintainers.
How to release
Use this section when you want to ship a new version. For pipeline details, see How it works below.
Before you start
ghinstalled and authenticated (gh auth login) — required for the local path only- Push access to
main - Clean working tree (no uncommitted changes)
- Version string ready:
MAJOR.MINOR.PATCH, with optional-suffix(e.g.0.1.4-alpha→ tagv0.1.4-alpha)
Recommended: release locally
- From the repository root, run release.py with the new version.
- Confirm when prompted (skip with
--yes). - Wait for GitHub Actions. Upload Python Package and Deploy Release Docs to GitHub Pages should start after the GitHub release is published.
python .github/release.py 0.1.4 # interactive confirm
python .github/release.py 0.1.4 --yes # non-interactive
python .github/release.py 0.1.4 --dry-run
If the version bump is already on main but the GitHub release was not created:
python .github/release.py --publish-only --yes
Alternative: release via GitHub Actions
- Open Actions → Release xlm-core → Run workflow.
- Enter the version (and optionally enable dry run).
- Wait for the release job to finish, then for Upload Python Package and Deploy Release Docs to GitHub Pages.
Both downstream workflows should appear in Actions after a successful non-dry-run release.
If something went wrong
| Situation | What to run |
|---|---|
Version already on main, no GitHub release |
python .github/release.py --publish-only --yes |
| Release exists but PyPI upload missing | Actions → Upload Python Package → Run workflow, tag_name=vX.Y.Z |
| Release exists but docs missing | Actions → Deploy Release Docs to GitHub Pages → Run workflow, same tag |
| xlm-core uploaded but xlm-models failed | Re-run Upload Python Package with the same tag (twine may skip the existing xlm-core upload) |
Manual release (legacy)
- Update defaults in version.py and xlm-models/version.py (e.g.
0.1.2). - Commit and push to
main. - Create a GitHub release with tag
v0.1.2on that commit and publish it.
Version format
Supported: MAJOR.MINOR.PATCH with optional pre-release suffix -label (e.g. 0.1.4-alpha). Tags use a v prefix (v0.1.4).
How it works
This section is for maintainers debugging releases or changing workflows.
Pipeline overview
A release bumps the version on main, creates a GitHub release tag, then triggers PyPI upload (both packages) and docs deployment.
flowchart LR
subgraph procedure [HowToRelease]
A[Run release.py or Actions workflow]
B[GitHub release tag vX.Y.Z]
end
subgraph downstream [DownstreamWorkflows]
C[publish.yml builds and uploads xlm-core]
C2[publish.yml builds and uploads xlm-models]
D[docs-release.yml deploys mike docs]
end
A --> B
B --> C
B --> C2
B --> D
releaseYml[release.yml] -->|"chains via workflow_dispatch when using GITHUB_TOKEN"| C
releaseYml --> C2
releaseYml --> D
localGh[local gh release] -->|"release published event"| C
localGh --> C2
localGh --> D
| Workflow | File | Normal trigger | Effect |
|---|---|---|---|
| Release xlm-core | release.yml | workflow_dispatch |
Runs release.py |
| Upload Python Package | publish.yml | release: published or manual |
Build wheel/sdist for xlm-core and xlm-models; upload both to PyPI |
| Deploy Release Docs | docs-release.yml | release: published or manual |
Deploy versioned docs via mike |
What release.py does
release.py is used locally and from release.yml. It:
- Patches default values in version.py and xlm-models/version.py
- Verifies parsed
VERSIONmatches the requested release in both files - Commits and pushes to
main - Runs
gh release create v<version>
The GitHub release tag is always v plus the version in version.py after the bump, so the tag and files stay aligned.
Local vs GitHub Actions release paths
How PyPI and docs workflows start depends on where the GitHub release is created.
Local gh release create (your OAuth/PAT): GitHub emits release: published. publish.yml and docs-release.yml start automatically.
Release created inside Actions via GITHUB_TOKEN: GitHub does not emit release: published to other workflows. release.yml therefore chains PyPI and docs explicitly via gh workflow run ... after a non-dry-run release.
This is why a release run from Actions still triggers upload and docs deploy, even though the event path differs from a local release.
Version sources
Two layers matter:
version.pydefaults — source of truth onmainafter the bump. setup.py readsVERSIONfromsrc/xlm/version.pyviaexec(). xlm-models/setup.py reads from xlm-models/version.py and pinsinstall_requirestoxlm-core==VERSION.- Release tag + env vars — publish.yml extracts
XLM_CORE_VERSION_*from the tag at build time. These override defaults during the PyPI build for both packages.
Keeping both version.py files in sync avoids confusion when installing from main.
Required secrets
One-time repo setup under Settings → Secrets and variables → Actions:
PYPI_USERNAME— PyPI usernamePYPI_TOKEN— PyPI API token
xlm-models
xlm-models/setup.py is published alongside setup.py by publish.yml. Both packages share the same version number. xlm-models declares install_requires=[f"xlm-core=={VERSION}"], so pip install xlm-models==X.Y.Z always pulls the matching xlm-core release. publish.yml uploads xlm-core first, then xlm-models.