Publishing packages to PyPI¶
Sandloom publishes five distributions through
Release (PyPI):
sandloom, sandloom-identity, sandloom-tenancy, sandloom-billing and
sandloom-admin.
Versions are independent. A manual run can select one package or all for a
coordinated release. A per-package tag selects only that package.
The current published alpha is 0.7.0a6 for all five packages.
Authentication and approval¶
The publish job uses PyPI Trusted Publishing with GitHub OIDC. No permanent PyPI API token, username or password is stored in GitHub. Configure each PyPI project's Trusted Publisher with these exact public values. A project that doesn't exist yet needs a pending publisher, added on PyPI under your account before the first release:
| Field | Value |
|---|---|
| Owner | Sandloom-dev |
| Repository | sandloom |
| Workflow filename | release.yml |
| Environment | pypi |
The GitHub pypi environment requires approval by Maryarty, including runs
started by the owner. Administrator bypass is disabled. The owner may approve
their own run because there is only one maintainer. Allowed deployment refs are
main and the four tag patterns sandloom-v*, sandloom-identity-v*,
sandloom-tenancy-v* and sandloom-billing-v*.
The plan step also refuses publication from a feature branch or a tag whose
commit is not reachable from origin/main. A tag must exactly match the selected
package's version in its pyproject.toml. Merging a PR never publishes packages.
Build and validation¶
- The plan validates the event, ref, package selection and versions.
- Full framework CI runs, including the compatibility matrix and heavy scenarios.
- The package workflow builds every wheel and standalone source archive, checks licenses, compares direct and rebuilt wheels, enforces import layering and tests clean framework-only and standard installations.
- It preserves those exact validated archives and their SHA-256 digests, then runs strict Twine metadata/description checks. The preparation job selects the requested packages without rebuilding them.
- After owner approval, a separate publish job downloads the reviewed files,
verifies local hashes and checks for matching files already on PyPI. Only
missing files are uploaded. This job performs no source checkout, build or
dependency installation and is the only job with
id-token: write. - PyPI publication uses signed attestations. The final verification waits for the release to become visible and compares every filename and SHA-256 digest against the reviewed manifest.
Artifacts are retained for seven days. A differing existing file fails closed; the workflow never replaces an immutable PyPI file or silently accepts a hash mismatch. After an interrupted upload, a rerun reuses identical published files and uploads only the missing ones. A publication/verification failure must be investigated before choosing a new version.
GitHub Releases¶
After PyPI publication and hash verification succeed, a separate job creates
an initially private draft GitHub Release, uploads the same reviewed archives,
SHA256SUMS and the source manifest, verifies their hashes, then publishes it.
Release notes come from the selected version's changelog section, captured
before owner approval. Only this job receives contents: write; it has no
PyPI token or OIDC permission and does no checkout, build or installation.
A coordinated all release requires matching package versions and creates
v<version> (for example v0.7.0a6). A single-package release creates
<package>-v<version>. Alpha, beta and release candidates are marked as
GitHub prereleases. The tag must point to the manifest's exact source commit.
Reruns can fill missing assets in a draft but never overwrite a tag or a
conflicting asset. A matching published release is verified without changes.
A dry run never creates a tag, draft, GitHub Release or PyPI upload.
GitHub can briefly delay a newly created draft's visibility in REST reads.
The publisher retries that read up to six times, with two seconds between
attempts; it never repeats the create or upload command to handle this delay.
If the job still fails, inspect the draft and rerun only the github-release
job. Matching drafts and assets are resumed, while conflicting hashes or tags
still stop publication. A successful PyPI upload does not need to be repeated.
Manual release¶
For a coordinated release, run this from the repository root:
python tools/packaging/sync_release.py --version 0.7.0a6 --write
uv lock
uv lock --project benchmarks
uv run python tools/checks/check_benchmark_dependencies.py --write
The command above prepares the 0.7.0a6 release.
The canonical current framework version is packages/sandloom/pyproject.toml.
The helper updates all five package versions, compatible dependency floors,
example manifests, scaffold requirements, CLI help and current documentation.
It discovers package pins in source documentation; prose that describes the
current release carries an inline <!-- sandloom-release --> marker.
Changelogs, ADRs and the dependency security audit retain historical versions.
Move unreleased changes to the new changelog section and review the lock diffs;
these commands must not upgrade unrelated dependencies.
python tools/packaging/sync_release.py checks without writing. This guard runs
in required CI, before package validation and before a website build. It uses
checked-in metadata, so builds need no PyPI request to determine a version.
Module packages may still release independently: without --version, their
recorded versions are retained and references use their own package metadata.
A release PR describes its candidate version; deploy the site after the packages
are published and verified. Merging alone neither publishes nor deploys.
After the owner merges the PR, open Actions → Release (PyPI) → Run workflow:
- Select
mainand one package, orallfor the coordinated0.7.0a6release. - Leave dry_run checked for validation and artifacts without an upload.
- For publication, uncheck dry_run. The workflow still validates first and
stops at the
pypienvironment for owner approval before obtaining credentials. - Review the commit, validation results and artifact manifest before approving.
A dry run can validate a feature branch and never enters the publishing environment. A real publication from a feature branch is refused.
Tag release¶
A per-package tag on a reviewed main commit requests that package's release:
git tag sandloom-v0.7.0a6
git push origin sandloom-v0.7.0a6
The other package prefixes are sandloom-identity-v, sandloom-tenancy-v and
sandloom-billing-v. sandloom-admin releases through a coordinated manual run;
its tag and single-package choice need an entry in release.yml. Release a framework version before module versions that
require it, or use one coordinated manual run. Each tag-triggered upload still
requires the owner's environment approval.
Package metadata and licenses¶
Short descriptions come from description and long descriptions from readme
in each package's pyproject.toml. PyPI release metadata is immutable: changing
an already-published description requires a new release. Validate the rendered
metadata with twine check --strict before uploading.
Each package declares license = "Apache-2.0" and
license-files = ["LICENSE", "NOTICE"]. The build hook rejects package copies
that differ from the canonical root files. Standalone sdists and wheels retain
those files and third-party notices. See packages/README.md for build details.
Testing source changes in an application¶
Use sandloom new --framework-source path --framework-path <checkout> and
uv sync to test local source changes. The path must name the repository root
containing packages/. These editable paths are local dependencies, not portable
Docker or CI inputs. --framework-source git is disabled; use published packages
or a complete matching wheel set for portable deployments.