Skip to content

[META]: Generate package metadata dynamically for PyPI#95

Open
spencrr wants to merge 3 commits into
microsoft:mainfrom
spencrr:dev/spencrr/dynamic-readme-image-permalink-and-version
Open

[META]: Generate package metadata dynamically for PyPI#95
spencrr wants to merge 3 commits into
microsoft:mainfrom
spencrr:dev/spencrr/dynamic-readme-image-permalink-and-version

Conversation

@spencrr

@spencrr spencrr commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description

Move RAMPART package metadata generation to Hatchling so release artifacts can produce PyPI-safe metadata without requiring manual README or version edits.

Dynamic README metadata

The source README.md now keeps repository image links relative, while package builds generate PyPI README metadata with raw GitHub image URLs. This keeps the README readable in GitHub and makes images render reliably on PyPI. The build-time README generation uses Hatch’s dynamic metadata hook pattern (Hatch dynamic metadata, custom metadata hooks).

Dynamic version metadata

Package versions are now derived from Git tags through Hatch VCS instead of a static project.version field. Release builds get their version from tags like vX.Y.Z; development builds after a tag infer a development version. Local version suffixes such as +g<sha> are omitted for public-package compatibility via local_scheme = "no-local-version" (hatch-vcs version source, setuptools-scm local schemes).

The release process docs were updated to describe tag-derived versions, relative README image links, and the requirement that release tags be reachable from main history for expected development-version inference.

Package data inclusion

The setuptools-specific prompt inclusion configuration was removed as part of the Hatchling migration:

[tool.setuptools.package-data]
rampart = [
    "drivers/prompts/*.yaml",
    "evaluators/prompts/*.yaml",
]

Hatchling does not use tool.setuptools.package-data. Instead, it uses Hatch’s file-selection model. With:

[tool.hatch.build.targets.wheel]
packages = ["rampart"]

the wheel target explicitly selects the rampart/ package directory. Hatch documents packages as semantically equivalent to only-include, with package paths rewritten as needed. Selected directories are traversed by default, subject to normal VCS ignore and exclude behavior.

I verified the built wheel directly and confirmed the prompt YAML files are still present:

rampart/drivers/prompts/llm_driver_system_prompt.yaml
rampart/evaluators/prompts/llm_judge.yaml

This preserves the previous package data behavior while removing configuration that only applied to the old setuptools backend. References: Hatch file selection, Hatch packages, and wheel default file selection.

Breaking changes

None. This changes package build metadata generation only; runtime APIs and package contents are unchanged.

Checklist

  • pre-commit run --all-files passes
  • Tests added or updated for changes - existing unit tests cover runtime behavior; package build and wheel metadata inspection verify the packaging changes.
  • Documentation updated - release process docs now cover dynamic versioning and README metadata behavior.

Verification

  • uv lock --check: passes
  • uv run ruff check scripts/hatch_build.py: passes
  • uv run --with build python -m build: passes
  • uv run pytest tests/unit -q: 489 passed
  • Wheel metadata inspection:
    • Generated by Hatchling
    • Version inferred dynamically without a local +g<sha> suffix, instead .dev{commits_since_tag}
    • README image path rewritten from relative source path to a raw GitHub URL

@spencrr spencrr requested a review from a team June 22, 2026 21:22
@spencrr spencrr enabled auto-merge (squash) July 7, 2026 20:33
Comment thread pyproject.toml
"evaluators/prompts/*.yaml",
]
[tool.hatch.version]
source = "vcs"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI find


Deriving the version from VCS assumes a release tag is reachable from the branch being built. Our release process puts vX.Y.Z on a releases/vX.Y branch and doesn't merge it to main, so that assumption doesn't hold here:

  • git merge-base --is-ancestor v0.1.0 origin/main → fails (tag isn't an ancestor of main)
  • git describe --tags HEAD → "No tags can describe HEAD"
  • uv build --wheelrampart-0.1.dev75, and git rev-list --count HEAD is also 75 → counting from repo root, no tag found
  • packaging.Version("0.1.dev75") < Version("0.1.0")True

So main builds sort before the shipped 0.1.0, and stay in 0.1.devN even after future releases. Note fallback_version won't help — it only applies when there's no .git at all (e.g. building from an sdist); a main checkout has git metadata but no reachable tag, so setuptools-scm uses its no-tag counting instead.

Fix: make release tags reachable from main — either tag a commit that's on main, or add a release step that merges the tagged release commit back into main (a real merge, not cherry-pick). I tested this: with the tag reachable, git describe v0.1.0-35-g… and the build produces 0.1.1.dev35, which correctly sorts after 0.1.0. Please confirm the intended flow lands the tag on main before this merges.


The `no-local-version` setting omits local version suffixes such as `+g<sha>` because PyPI does not support them for upstream releases. See the [setuptools-scm local scheme documentation](https://setuptools-scm.readthedocs.io/en/latest/extending/#setuptools_scmlocal_scheme) for details.

For development builds on `main`, the release tag must be reachable from `main` history for Hatch VCS to infer the next development version from that tag. If the release branch contains commits beyond `main`, merge or cherry-pick those release commits back to `main` after publishing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI find


"merge or cherry-pick those release commits back to main" — cherry-pick creates new commits with new SHAs, so the tagged commit stays unreachable and git describe still won't find it. Only merging the tagged commit (or tagging something already on main) satisfies setuptools-scm. This is the same root cause as the version blocker above.

Also, this guidance sits in §4 (before the tag is even created in §5), and neither §5 nor §6 adds a step that brings the tag onto main — which is why v0.1.0 isn't on main today. Please add an explicit "merge the release tag into main" step and drop the cherry-pick wording.

Comment thread scripts/hatch_build.py
)


def _readme_ref(version: str) -> str:

@nina-msft nina-msft Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding unit tests for these:

_readme_ref, _raw_image_url, and _render_readme are pure and deterministic but untested, and a regex regression (an image silently not rewritten, or a mangled README) would ship without a signal. They're trivially importable — worth a small tests/ module covering: dev version → main ref; release version → vX.Y.Z ref; HTML src="docs/images/…" and ./docs/images/…; markdown ![alt](docs/images/…); the already-absolute main URL patterns; and no-image passthrough.

Comment thread scripts/hatch_build.py

from hatchling.metadata.plugin.interface import MetadataHookInterface

_GITHUB_IMAGE_URL_PATTERNS = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Only docs/images/… paths are rewritten, so a relative image elsewhere (e.g. assets/…) would pass through and 404 on PyPI. The markdown pattern (docs/images/[^)]+) would also pull an optional title into the URL for ![a](docs/images/x.png "t"). Neither bites the current README — just flagging in case image usage grows. A one-line comment pinning the docs/images/ assumption would be enough.

@nina-msft nina-msft left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this deviates from our alignment with PyRIT's release process. Do we think if it is sucessful with RAMPART we will also update PyRIT's process to automate these parts?

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.

2 participants