Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="https://github.com/microsoft/RAMPART/raw/main/docs/images/RAMPART.svg" alt="RAMPART Logo" width="300"/>
<img src="docs/images/RAMPART.svg" alt="RAMPART Logo" width="300"/>
</p>

<h1 align="center">RAMPART</h1>
Expand Down
28 changes: 17 additions & 11 deletions docs/contributing/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,29 @@ If you find functionality to remove, merge the removal PR to `main` before proce

## 4. Update the Version

### pyproject.toml
Set the version in `pyproject.toml` to the version established in step 2.
### Git tag
RAMPART derives package versions from Git tags using Hatch VCS and setuptools-scm. No `pyproject.toml` version bump is required for a release. The release version is determined by the `vx.y.z` tag pushed in step 5.

```toml
[project]
name = "RAMPART"
version = "x.y.z"
[tool.hatch.version]
source = "vcs"

[tool.hatch.version.raw-options]
local_scheme = "no-local-version"
```

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.

### Update README File
The README file is published to PyPI and also needs to be updated so the links work properly. _Note: There may not be any links to update, but it is good practice to check in case our README changes._

Replace all “main” links like “doc/index.md” with “raw” links that have the correct version number, i.e., “https://raw.githubusercontent.com/microsoft/RAMPART/releases/vx.y.z/docs/index.md”.
Keep README image links relative when they point to files in this repository, e.g., `docs/images/RAMPART.svg`. During package builds, `scripts/hatch_build.py` generates the PyPI README metadata and rewrites those image paths to raw GitHub URLs with the release version.

For images, update using the “raw” link, e.g., “https://raw.githubusercontent.com/microsoft/RAMPART/releases/vx.y.z/docs/images/RAMPART.png”.
Replace any other "main" links like "doc/index.md" with "raw" links that have the correct version number, i.e., "https://raw.githubusercontent.com/microsoft/RAMPART/releases/vx.y.z/docs/index.md".

For directories, update using the tree link, e.g., https://github.com/microsoft/RAMPART/tree/releases/vx.y.z/docs/usage"
For directories, update using the "tree" link, e.g., "https://github.com/microsoft/RAMPART/tree/releases/vx.y.z/docs/usage"

This is required for the release branch because PyPI does not pick up other files besides the README, which results in local links breaking.

Expand Down Expand Up @@ -149,7 +155,7 @@ If successful, the URL `https://pypi.org/project/rampart/x.y.z/` will return the

After the release is on PyPI, open a PR to `main` containing only:

- In line with PyPA [versioning guidance](https://packaging.python.org/en/latest/discussions/versioning/), bump the version in `pyproject.toml` to the next development version (e.g., `x.y.(z+1).dev0` or `x.(y+1).0.dev0`, depending on the next planned release).
- Any follow-up documentation or metadata updates needed after the release. Do not bump the package version in `pyproject.toml`; once `main` has commits after the release tag, Hatch VCS will infer the next development version automatically.
- Replace any references to the previous release version in the codebase with the new released version (without `.dev0`) where applicable (e.g., installation docs that pin to the latest tag).

Open this PR from a branch separate from your `releases/vx.y.z` branch.
Expand Down Expand Up @@ -216,10 +222,10 @@ A patch release (e.g., `0.2.0` → `0.2.1`) ships a targeted fix — typically a

Resolve any conflicts manually. Patch-sized fixes typically apply cleanly.

3. **Bump the version** in `pyproject.toml` to the new patch version. Also update any version-pinned links in `README.md`.
3. **Update release-specific references** as needed. Do not bump the package version in `pyproject.toml`; the patch version comes from the `vx.y.z` tag. Also update any version-pinned links in `README.md`.

```bash
git commit -am "Bump version to x.y.z"
git commit -am "Prepare x.y.z release"
```

4. **Push and tag**:
Expand Down
31 changes: 19 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
requires = ["hatchling>=1.30.1", "hatch-vcs>=0.5.0"]
build-backend = "hatchling.build"

[project]
name = "RAMPART"
version = "0.1.1.dev0"
description = "A pytest-native safety testing framework for agentic AI applications"
readme = "README.md"
dynamic = ["readme", "version"]
license = "MIT"
requires-python = ">=3.11"
authors = [
Expand Down Expand Up @@ -42,6 +41,8 @@ onedrive = [

[dependency-groups]
dev = [
"hatch-vcs>=0.5.0",
"hatchling>=1.30.1",
"pre-commit>=4.5.1",
"pytest-cov>=6.1.0",
"pytest-xdist[psutil]>=3.8.0",
Expand All @@ -63,14 +64,17 @@ Issues = "https://github.com/microsoft/RAMPART/issues"
[project.entry-points.pytest11]
rampart = "rampart.pytest_plugin.plugin"

[tool.setuptools.packages.find]
exclude = ["site*", "docs*", "tests*", "scripts*"]
[tool.hatch.metadata.hooks.custom]
path = "scripts/hatch_build.py"

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

[tool.hatch.version.raw-options]
local_scheme = "no-local-version"

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

[tool.coverage.run]
source = ["rampart"]
Expand All @@ -97,6 +101,9 @@ filterwarnings = [
select = ["ALL"]

[tool.ruff.lint.per-file-ignores]
"scripts/hatch_build.py" = [
"INP001", # Top-level build hook
]
"tests/**" = [
"S101", # assert is pytest's API
"D100", "D101", "D102", "D104", "D107", # no docstrings needed
Expand Down Expand Up @@ -130,7 +137,7 @@ max-args = 10
python-version = "3.11"

[tool.ty.src]
include = ["rampart", "tests"]
include = ["rampart", "tests", "scripts"]

[tool.uv.sources]
pyrit = { git = "https://github.com/microsoft/PyRIT", rev = "6dc8b94139757390286bbce7d53c1f7e58e66e29" } # v0.13.0
81 changes: 81 additions & 0 deletions scripts/hatch_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
"""Hatchling metadata hooks for RAMPART package builds."""

from __future__ import annotations

import re
from pathlib import Path

from hatchling.metadata.plugin.interface import MetadataHookInterface

_GITHUB_IMAGE_URL_PATTERNS = (
re.compile(r"(https://github\.com/microsoft/RAMPART/raw/)main(/docs/images/)"),
re.compile(
r"(https://raw\.githubusercontent\.com/microsoft/RAMPART/)main(/docs/images/)",
),
)
_RELATIVE_HTML_IMAGE_URL_PATTERNS = (
re.compile(r'(<img\b[^>]*\bsrc=")(?:\./)?(docs/images/[^"]+)(")'),
re.compile(r"(<img\b[^>]*\bsrc=')(?:\./)?(docs/images/[^']+)(')"),
)
_RELATIVE_MARKDOWN_IMAGE_URL_PATTERN = re.compile(
r"(!\[[^\]]*\]\()(?:\./)?(docs/images/[^)]+)(\))",
)


def _readme_ref(version: str) -> str:
"""Return the Git ref to use for README image URLs."""
if ".dev" in version or "+" in version:
return "main"

return f"v{version}"


def _raw_image_url(*, readme_ref: str, image_path: str) -> str:
"""Return an absolute GitHub raw URL for a README image."""
return (
f"https://raw.githubusercontent.com/microsoft/RAMPART/{readme_ref}/{image_path}"
)


def _render_readme(*, root: Path, version: str) -> str:
"""Render README content for package metadata."""
readme = (root / "README.md").read_text(encoding="utf-8")
readme_ref = _readme_ref(version)

for pattern in _GITHUB_IMAGE_URL_PATTERNS:
readme = pattern.sub(rf"\g<1>{readme_ref}\g<2>", readme)

for pattern in _RELATIVE_HTML_IMAGE_URL_PATTERNS:
readme = pattern.sub(
lambda match: (
f"{match.group(1)}"
f"{_raw_image_url(readme_ref=readme_ref, image_path=match.group(2))}"
f"{match.group(3)}"
),
readme,
)

return _RELATIVE_MARKDOWN_IMAGE_URL_PATTERN.sub(
lambda match: (
f"{match.group(1)}"
f"{_raw_image_url(readme_ref=readme_ref, image_path=match.group(2))}"
f"{match.group(3)}"
),
readme,
)


class ReadmeMetadataHook(MetadataHookInterface):
"""Generate PyPI README metadata with release-pinned image URLs."""

def update(self, metadata: dict[str, object]) -> None:
"""Update project metadata in-place."""
metadata["readme"] = {
"content-type": "text/markdown",
"text": _render_readme(
root=Path(self.root),
version=str(metadata["version"]),
),
}
77 changes: 76 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading