Skip to content

Implement PyPI packaging; fix an install-breaking schema-path bug#12

Open
chaksaray wants to merge 5 commits into
developfrom
pypi
Open

Implement PyPI packaging; fix an install-breaking schema-path bug#12
chaksaray wants to merge 5 commits into
developfrom
pypi

Conversation

@chaksaray

Copy link
Copy Markdown
Member

Summary

Preparing this repo to actually publish to PyPI surfaced a real bug: _schemas.py
located schemas/ via Path(__file__).parent.parent.parent, which only resolves
inside an editable/source checkout. A real pip install bawbel-gate wheel does not
carry that path at all — every schema-dependent command (lint, verify, harden,
manifest loading in general) would raise FileNotFoundError for anyone installing
from PyPI rather than cloning the repo.

Confirmed by building the wheel and installing it into a clean venv:
bawbel-gate lint fails without the fix, passes with it.

  • Fix: bundle schemas/ into the wheel itself via
    [tool.hatch.build.targets.wheel.force-include], and make _schemas.py prefer
    that packaged copy over the repo-root path when both could resolve. Extracted the
    selection into _resolve_schema_dir() with two unit tests (packaged-present,
    packaged-missing), since the priority logic is exactly what was wrong.
  • pyproject.toml: complete PyPI metadata — readme, authors, classifiers,
    keywords, project.urls, SPDX license expression instead of the deprecated
    {text=...} form. Also adds mitigations/ to the sdist include list.
  • .github/workflows/publish.yml: release-triggered publish using PyPI
    trusted publishing (OIDC, no stored API token). Runs the full test suite and a
    real wheel-install smoke test (bawbel-gate --version from a fresh venv) before
    ever uploading.

What's NOT done here (can't be, from this repo)

Creating the PyPI project itself and configuring its trusted publisher
(owner/repo/workflow-filename/environment-name) at pypi.org requires the
maintainer's own PyPI account — I can't do that from here. Until that's set up and
a release is actually cut, README's "not yet on PyPI" status stays accurate; not
flipping it preemptively.

Test plan

  • pytest -- 329 passed
  • pre-commit run --all-files -- clean
  • python -m build -- succeeds (sdist + wheel)
  • twine check dist/* -- passes on both
  • Installed the built wheel into a throwaway venv and ran
    bawbel-gate lint against a real manifest -- confirms schema loading works
    from a real site-packages install, not just an editable checkout

_schemas.py located schemas/ via Path(__file__).parent.parent.parent,
which only resolves inside an editable/source checkout. A real
`pip install bawbel-gate` wheel does not carry that path at all --
every schema-dependent command (lint, verify, harden, manifest loading
in general) would have raised FileNotFoundError for anyone installing
from PyPI. Confirmed by building the wheel, installing it into a clean
venv, and running `bawbel-gate lint` against it: fails without the fix,
passes with it.

Fix: bundle schemas/ into the wheel itself via
[tool.hatch.build.targets.wheel.force-include], and make _schemas.py
prefer that packaged copy over the repo-root path when both could
resolve. Extracted the selection into _resolve_schema_dir() with two
unit tests (packaged-present, packaged-missing) since the priority
logic is exactly what was wrong.

Also completes pyproject.toml's PyPI metadata (readme, authors,
classifiers, keywords, project.urls, SPDX license expression instead of
the deprecated {text=...} form) and adds a release-triggered publish
workflow using PyPI trusted publishing (OIDC, no stored API token) that
runs the full test suite and a real wheel-install smoke test before
ever uploading.

Verified: `python -m build` succeeds, `twine check dist/*` passes on
both sdist and wheel, wheel installed + exercised in a throwaway venv,
329 tests passing, pre-commit clean.

Not done here, and can't be from this repo: creating the PyPI project
itself and configuring its trusted publisher (repo/workflow/environment
name) at pypi.org -- that requires the maintainer's own PyPI account.
Until that's done and a release is actually cut, README's "not yet on
PyPI" status stays accurate; not flipping it preemptively.
publish-testpypi runs automatically after build (skip-existing so a
re-run of the workflow doesn't hard-fail on a version already there).
publish-pypi now needs publish-testpypi and targets the "pypi"
environment, which has a required_reviewers protection rule -- that's
what actually pauses the job for manual approval, not anything in this
file. Both environments (testpypi unprotected, pypi with chaksaray as
required reviewer, self-review allowed since this is solo-maintained)
are already configured on the repo.
Verify Tests / Build Distribution / Publish to TestPyPI / Publish to PyPI,
for a consistent job graph across the bawbel repos. Behavior unchanged --
the pypi environment's required_reviewers rule still gates the last job.
Adds ~15 more PyPI search keywords covering the actual surface area
(mcp-server, agentic-ai, taint-tracking, capability-based-security,
supply-chain-security, etc.) instead of the original 6.

While checking sdist size, found that hatchling's sdist `include` key
only adds to its default "everything not gitignored" file set -- it
does not replace it. Since .agents/ (148K of untracked Claude Code
skill files, never part of this project) isn't in .gitignore, it was
silently ending up in the sdist tarball. Switched to `only-include`,
which is a strict allowlist and ignores the default VCS-based set
entirely.

This means the sdist's contents no longer depend on whatever untracked
files happen to be sitting in the builder's working directory --
actually reproducible, not just small.

Verified: wheel already had zero stray files (packages= is a proper
allowlist by nature, this was sdist-only). Rebuilt after both changes:
twine check passes on both artifacts, wheel installed and exercised in
a throwaway venv, 329 tests passing, pre-commit clean. Wheel ~77KB,
sdist ~94KB either way -- the leak was small in bytes, but the
nondeterminism was the real bug.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant