Skip to content

modelaudit release by @mldangelo #588

modelaudit release by @mldangelo

modelaudit release by @mldangelo #588

name: release-please
run-name: modelaudit release by @${{ github.actor }}
concurrency:
group: release-please-${{ github.ref }}
cancel-in-progress: false
on:
push:
branches:
- main
workflow_dispatch:
inputs:
root_version:
description: "Publish an already-versioned root modelaudit release, for example 0.2.39"
required: false
type: string
picklescan_version:
description: "Publish an already-versioned modelaudit-picklescan release, for example 0.1.2"
required: false
type: string
jobs:
release-please:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.manual.outputs.manual_release == 'true' && steps.manual.outputs.release_created || steps.release.outputs.release_created }}
tag_name: ${{ steps.manual.outputs.manual_release == 'true' && steps.manual.outputs.tag_name || steps.release.outputs.tag_name }}
version: ${{ steps.manual.outputs.manual_release == 'true' && steps.manual.outputs.version || steps.release.outputs.version }}
picklescan_release_created: ${{ steps.manual.outputs.manual_release == 'true' && steps.manual.outputs.picklescan_release_created || steps.release.outputs['packages/modelaudit-picklescan--release_created'] }}
picklescan_tag_name: ${{ steps.manual.outputs.manual_release == 'true' && steps.manual.outputs.picklescan_tag_name || steps.release.outputs['packages/modelaudit-picklescan--tag_name'] }}
picklescan_version: ${{ steps.manual.outputs.manual_release == 'true' && steps.manual.outputs.picklescan_version || steps.release.outputs['packages/modelaudit-picklescan--version'] }}
pr_number: ${{ steps.release.outputs.pr && fromJSON(steps.release.outputs.pr).number || '' }}
steps:
- name: Resolve manual release inputs
id: manual
env:
ROOT_VERSION: ${{ github.event.inputs.root_version || '' }}
PICKLESCAN_VERSION: ${{ github.event.inputs.picklescan_version || '' }}
run: |
{
if [[ -n "$ROOT_VERSION" ]]; then
echo "release_created=true"
echo "version=$ROOT_VERSION"
echo "tag_name=v$ROOT_VERSION"
else
echo "release_created=false"
fi
if [[ -n "$PICKLESCAN_VERSION" ]]; then
echo "picklescan_release_created=true"
echo "picklescan_version=$PICKLESCAN_VERSION"
echo "picklescan_tag_name=modelaudit-picklescan-v$PICKLESCAN_VERSION"
else
echo "picklescan_release_created=false"
fi
if [[ -n "$ROOT_VERSION" || -n "$PICKLESCAN_VERSION" ]]; then
echo "manual_release=true"
else
echo "manual_release=false"
fi
} >> "$GITHUB_OUTPUT"
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4
if: steps.manual.outputs.manual_release != 'true'
id: release
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Ensure manual GitHub releases exist
if: steps.manual.outputs.manual_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ROOT_TAG: ${{ steps.manual.outputs.tag_name }}
ROOT_VERSION: ${{ steps.manual.outputs.version }}
PICKLESCAN_TAG: ${{ steps.manual.outputs.picklescan_tag_name }}
PICKLESCAN_VERSION: ${{ steps.manual.outputs.picklescan_version }}
run: |
if [[ -n "$ROOT_VERSION" ]] && ! gh release view "$ROOT_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release create "$ROOT_TAG" \
--repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
--title "$ROOT_TAG" \
--notes "Manual recovery release for modelaudit $ROOT_VERSION."
fi
if [[ -n "$PICKLESCAN_VERSION" ]] && ! gh release view "$PICKLESCAN_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release create "$PICKLESCAN_TAG" \
--repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
--title "modelaudit-picklescan: v$PICKLESCAN_VERSION" \
--notes "Manual recovery release for modelaudit-picklescan $PICKLESCAN_VERSION."
fi
# Format changelogs immediately after release-please creates/updates a PR
# This runs for BOTH new PRs (prs_created) and existing PRs (pr output exists)
- name: Check if PR exists
id: check-pr
run: |
PR_OUTPUT='${{ steps.release.outputs.pr }}'
if [ -n "$PR_OUTPUT" ] && [ "$PR_OUTPUT" != "null" ]; then
echo "has_pr=true" >> "$GITHUB_OUTPUT"
echo "pr_branch=$(echo '${{ steps.release.outputs.pr }}' | jq -r '.headBranchName')" >> "$GITHUB_OUTPUT"
else
echo "has_pr=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
if: steps.check-pr.outputs.has_pr == 'true'
with:
ref: ${{ steps.check-pr.outputs.pr_branch }}
# Shallow clone for speed - we only need to format and push
fetch-depth: 1
- name: Install uv
if: steps.check-pr.outputs.has_pr == 'true'
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
- name: Install Rust toolchain for standalone lock refresh
if: steps.check-pr.outputs.has_pr == 'true'
run: |
rustup toolchain install stable --profile minimal
rustup default stable
- name: Sync uv.lock with pyproject.toml
if: steps.check-pr.outputs.has_pr == 'true'
run: |
uv lock
if git diff --quiet uv.lock; then
echo "✓ uv.lock already in sync"
else
echo "→ uv.lock updated to match pyproject.toml version bump"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add uv.lock
git commit -m "chore: sync uv.lock with pyproject.toml version bump"
git push
echo "✓ uv.lock committed and pushed"
fi
- name: Sync standalone package lock with pyproject.toml
if: steps.check-pr.outputs.has_pr == 'true'
working-directory: packages/modelaudit-picklescan
run: |
uv lock
if git diff --quiet uv.lock; then
echo "✓ packages/modelaudit-picklescan/uv.lock already in sync"
else
echo "→ packages/modelaudit-picklescan/uv.lock updated to match pyproject.toml"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add uv.lock
git commit -m "chore: sync standalone package lock"
git push
echo "✓ packages/modelaudit-picklescan/uv.lock committed and pushed"
fi
- name: Setup Node.js
if: steps.check-pr.outputs.has_pr == 'true'
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: "24"
- name: Install Node dependencies
if: steps.check-pr.outputs.has_pr == 'true'
run: npm ci --ignore-scripts
- name: Format changelogs with Prettier
if: steps.check-pr.outputs.has_pr == 'true'
run: |
echo "Formatting changelogs on branch: ${{ steps.check-pr.outputs.pr_branch }}"
npx prettier --write CHANGELOG.md packages/modelaudit-picklescan/CHANGELOG.md
if git diff --quiet CHANGELOG.md packages/modelaudit-picklescan/CHANGELOG.md; then
echo "✓ No formatting changes needed"
else
echo "→ Formatting changes detected, committing..."
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add CHANGELOG.md packages/modelaudit-picklescan/CHANGELOG.md
git commit -m "chore: format changelogs with prettier"
git push
echo "✓ Formatting committed and pushed"
fi
build:
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
needs: release-please
permissions:
contents: read
outputs:
artifact-name: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- name: Install Rust toolchain
run: |
rustup toolchain install stable --profile minimal
rustup default stable
- name: Sync dependencies
run: uv sync --extra all-ci
- name: Lint root package with Ruff
run: uv run ruff check modelaudit/ tests/
- name: Check root package formatting with Ruff
run: uv run ruff format --check modelaudit/ tests/
- name: Type check root package with mypy
run: uv run mypy modelaudit/ tests/
- name: Run root package tests
run: |
uv run pytest tests -n auto -m "not slow and not integration and not performance" --tb=short
- name: Build package
run: uv build
- name: Build standalone pickle package for smoke installs
run: uv build packages/modelaudit-picklescan --out-dir /tmp/modelaudit-picklescan-dist
- name: Verify distribution artifacts and version consistency
run: |
set -euo pipefail
EXPECTED_VERSION="${{ needs.release-please.outputs.version }}"
shopt -s nullglob
wheels=(dist/*.whl)
sdists=(dist/*.tar.gz)
echo "Built artifacts:"
ls -la dist/
if [[ ${#wheels[@]} -ne 1 ]]; then
echo "ERROR: Expected exactly 1 wheel artifact, found ${#wheels[@]}"
exit 1
fi
if [[ ${#sdists[@]} -ne 1 ]]; then
echo "ERROR: Expected exactly 1 sdist artifact, found ${#sdists[@]}"
exit 1
fi
WHEEL_PATH="${wheels[0]}"
SDIST_PATH="${sdists[0]}"
WHEEL_FILE="$(basename "${WHEEL_PATH}")"
SDIST_FILE="$(basename "${SDIST_PATH}")"
# Extract versions from package metadata, not filenames.
WHEEL_VERSION="$(
python - "${WHEEL_PATH}" <<'PY'
import sys
import zipfile
wheel_path = sys.argv[1]
with zipfile.ZipFile(wheel_path) as zf:
metadata_files = [name for name in zf.namelist() if name.endswith(".dist-info/METADATA")]
if len(metadata_files) != 1:
raise SystemExit(f"Expected exactly one wheel METADATA file, found {len(metadata_files)}")
metadata_text = zf.read(metadata_files[0]).decode("utf-8", errors="replace")
for line in metadata_text.splitlines():
if line.startswith("Version: "):
print(line.split(":", 1)[1].strip())
break
else:
raise SystemExit("Could not find Version header in wheel METADATA")
PY
)"
SDIST_VERSION="$(
python - "${SDIST_PATH}" <<'PY'
import sys
import tarfile
sdist_path = sys.argv[1]
with tarfile.open(sdist_path, "r:gz") as tf:
pkg_info_files = [m for m in tf.getmembers() if m.name.endswith("/PKG-INFO")]
if len(pkg_info_files) != 1:
raise SystemExit(f"Expected exactly one PKG-INFO file in sdist, found {len(pkg_info_files)}")
file_obj = tf.extractfile(pkg_info_files[0])
if file_obj is None:
raise SystemExit("Failed to read PKG-INFO from sdist")
pkg_info_text = file_obj.read().decode("utf-8", errors="replace")
for line in pkg_info_text.splitlines():
if line.startswith("Version: "):
print(line.split(":", 1)[1].strip())
break
else:
raise SystemExit("Could not find Version header in sdist PKG-INFO")
PY
)"
if [[ "$WHEEL_VERSION" != "$EXPECTED_VERSION" || "$SDIST_VERSION" != "$EXPECTED_VERSION" ]]; then
echo "ERROR: Artifact versions do not match expected release version"
echo "Expected version: $EXPECTED_VERSION"
echo "Wheel version: $WHEEL_VERSION ($WHEEL_FILE)"
echo "sdist version: $SDIST_VERSION ($SDIST_FILE)"
exit 1
fi
echo "Artifact validation passed for version ${EXPECTED_VERSION}"
- name: Validate package metadata
run: uvx twine check dist/*
- name: Smoke test wheel install in clean environment
run: |
set -euo pipefail
EXPECTED_VERSION="${{ needs.release-please.outputs.version }}"
shopt -s nullglob
wheel_artifacts=(dist/modelaudit-*.whl)
if [[ ${#wheel_artifacts[@]} -ne 1 ]]; then
echo "ERROR: Expected exactly 1 wheel artifact for install smoke test, found ${#wheel_artifacts[@]}"
ls -la dist/
exit 1
fi
WHEEL_ARTIFACT="${wheel_artifacts[0]}"
uv venv /tmp/modelaudit-wheel-smoke
uv pip install \
--python /tmp/modelaudit-wheel-smoke/bin/python \
--find-links /tmp/modelaudit-picklescan-dist \
"${WHEEL_ARTIFACT}"
INSTALLED_VERSION="$(
/tmp/modelaudit-wheel-smoke/bin/python -c "import importlib.metadata as m; print(m.version('modelaudit'))"
)"
if [[ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]]; then
echo "ERROR: Wheel install version mismatch: expected $EXPECTED_VERSION, got $INSTALLED_VERSION"
exit 1
fi
# Validate required project URLs in installed metadata.
/tmp/modelaudit-wheel-smoke/bin/python - <<'PY'
import importlib.metadata as md
# Keep these expected URLs in sync with [project.urls] in pyproject.toml.
required = {
"Bug Tracker": "https://github.com/promptfoo/modelaudit/issues",
"Changelog": "https://github.com/promptfoo/modelaudit/blob/main/CHANGELOG.md",
}
project_urls = md.metadata("modelaudit").get_all("Project-URL") or []
parsed = {}
for entry in project_urls:
if "," in entry:
label, url = entry.split(",", 1)
parsed[label.strip()] = url.strip()
missing = [label for label in required if label not in parsed]
mismatched = [label for label, expected in required.items() if parsed.get(label) != expected]
if missing or mismatched:
raise SystemExit(
f"Project URL metadata invalid. missing={missing}, mismatched={mismatched}, parsed={parsed}"
)
print("Project URL metadata validated.")
PY
/tmp/modelaudit-wheel-smoke/bin/modelaudit --version
/tmp/modelaudit-wheel-smoke/bin/python - <<'PY'
import modelaudit_picklescan
report = modelaudit_picklescan.scan_bytes(b"\x80\x04}q\x00.")
assert report.status.value == "complete", report
print("modelaudit_picklescan import and scan smoke test passed.")
PY
# Basic CLI smoke run from the installed wheel.
/tmp/modelaudit-wheel-smoke/bin/python - <<'PY'
import pathlib
import pickle
import subprocess
import tempfile
with tempfile.TemporaryDirectory(prefix="modelaudit-wheel-smoke-") as tmpdir:
test_dir = pathlib.Path(tmpdir)
test_file = test_dir / "smoke.pkl"
with test_file.open("wb") as f:
pickle.dump({"smoke": True}, f)
completed = subprocess.run(
["/tmp/modelaudit-wheel-smoke/bin/modelaudit", str(test_file), "--format", "json"],
capture_output=True,
text=True,
check=False,
)
print(completed.stdout)
if completed.returncode not in (0, 1):
raise SystemExit(f"Unexpected modelaudit exit code during wheel smoke test: {completed.returncode}")
PY
- name: Smoke test sdist install in clean environment
run: |
set -euo pipefail
EXPECTED_VERSION="${{ needs.release-please.outputs.version }}"
shopt -s nullglob
sdist_artifacts=(dist/modelaudit-*.tar.gz)
if [[ ${#sdist_artifacts[@]} -ne 1 ]]; then
echo "ERROR: Expected exactly 1 sdist artifact for install smoke test, found ${#sdist_artifacts[@]}"
ls -la dist/
exit 1
fi
SDIST_ARTIFACT="${sdist_artifacts[0]}"
uv venv /tmp/modelaudit-sdist-smoke
uv pip install \
--python /tmp/modelaudit-sdist-smoke/bin/python \
--find-links /tmp/modelaudit-picklescan-dist \
"${SDIST_ARTIFACT}"
INSTALLED_VERSION="$(
/tmp/modelaudit-sdist-smoke/bin/python -c "import importlib.metadata as m; print(m.version('modelaudit'))"
)"
if [[ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]]; then
echo "ERROR: sdist install version mismatch: expected $EXPECTED_VERSION, got $INSTALLED_VERSION"
exit 1
fi
/tmp/modelaudit-sdist-smoke/bin/modelaudit --version
/tmp/modelaudit-sdist-smoke/bin/python - <<'PY'
import modelaudit_picklescan
report = modelaudit_picklescan.scan_bytes(b"\x80\x04}q\x00.")
assert report.status.value == "complete", report
print("modelaudit_picklescan import and scan smoke test passed.")
PY
# Basic CLI smoke run from the installed sdist.
/tmp/modelaudit-sdist-smoke/bin/python - <<'PY'
import pathlib
import pickle
import subprocess
import tempfile
with tempfile.TemporaryDirectory(prefix="modelaudit-sdist-smoke-") as tmpdir:
test_dir = pathlib.Path(tmpdir)
test_file = test_dir / "smoke.pkl"
with test_file.open("wb") as f:
pickle.dump({"smoke": True}, f)
completed = subprocess.run(
["/tmp/modelaudit-sdist-smoke/bin/modelaudit", str(test_file), "--format", "json"],
capture_output=True,
text=True,
check=False,
)
print(completed.stdout)
if completed.returncode not in (0, 1):
raise SystemExit(f"Unexpected modelaudit exit code during sdist smoke test: {completed.returncode}")
PY
- name: Upload build artifacts
id: upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dist
path: dist/
build-picklescan-package:
if: needs.release-please.outputs.picklescan_release_created == 'true'
name: Build standalone pickle package (${{ matrix.artifact-suffix }})
runs-on: ${{ matrix.os }}
needs: release-please
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
artifact-suffix: linux
build-sdist: "true"
- os: macos-14
artifact-suffix: macos-arm64
build-sdist: "false"
- os: macos-15-intel
artifact-suffix: macos-x86_64
build-sdist: "false"
- os: ubuntu-24.04-arm
artifact-suffix: linux-aarch64
build-sdist: "false"
- os: windows-latest
artifact-suffix: windows
build-sdist: "false"
defaults:
run:
shell: bash
working-directory: packages/modelaudit-picklescan
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- name: Pin Python version
run: |
uv python pin 3.12
- name: Install Rust toolchain
run: |
rustup toolchain install stable --profile minimal --component rustfmt --component clippy
rustup default stable
- name: Cache Cargo dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/modelaudit-picklescan/target
key: ${{ runner.os }}-cargo-picklescan-${{ hashFiles('packages/modelaudit-picklescan/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-picklescan-
- name: Check standalone package lock is in sync
run: |
uv lock --check
- name: Check Rust scanner formatting
run: cargo fmt --manifest-path Cargo.toml -- --check
- name: Check Rust scanner crate
run: cargo check --manifest-path Cargo.toml
- name: Lint Rust scanner crate
run: cargo clippy --manifest-path Cargo.toml --all-targets -- -D warnings
- name: Test Rust scanner crate
run: |
cargo test --manifest-path Cargo.toml
- name: Lint standalone package with Ruff
run: uv run --with ruff ruff check src tests
- name: Check standalone package formatting with Ruff
run: uv run --with ruff ruff format --check src tests
- name: Type check standalone package with mypy
run: uv run --with mypy mypy src tests
- name: Run standalone package tests
run: uv run --with pytest --with pytest-xdist pytest -n auto tests --tb=short
- name: Build standalone package sdist
if: matrix.build-sdist == 'true'
run: |
uv build --sdist --out-dir dist
- name: Build standalone package manylinux wheel
if: runner.os == 'Linux'
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1
with:
command: build
args: --release --out dist
manylinux: "2_28"
working-directory: packages/modelaudit-picklescan
- name: Build standalone package wheel
if: runner.os != 'Linux'
run: |
uv build --wheel --out-dir dist
- name: Validate standalone package metadata
run: uvx twine check dist/*
- name: Verify standalone artifact version consistency
run: |
set -euo pipefail
EXPECTED_VERSION="${{ needs.release-please.outputs.picklescan_version }}"
shopt -s nullglob
artifacts=(dist/modelaudit_picklescan-*.whl dist/modelaudit_picklescan-*.tar.gz)
if [[ ${#artifacts[@]} -eq 0 ]]; then
echo "ERROR: Expected at least one modelaudit_picklescan artifact"
ls -la dist/
exit 1
fi
for artifact in "${artifacts[@]}"; do
ARTIFACT_VERSION="$(
python - "${artifact}" <<'PY'
import sys
import tarfile
import zipfile
from pathlib import Path
artifact_path = Path(sys.argv[1])
if artifact_path.suffix == ".whl":
with zipfile.ZipFile(artifact_path) as zf:
metadata_files = [name for name in zf.namelist() if name.endswith(".dist-info/METADATA")]
if len(metadata_files) != 1:
raise SystemExit(f"Expected one wheel METADATA file, found {len(metadata_files)}")
metadata_text = zf.read(metadata_files[0]).decode("utf-8", errors="replace")
elif artifact_path.name.endswith(".tar.gz"):
with tarfile.open(artifact_path, "r:gz") as tf:
pkg_info_files = [member for member in tf.getmembers() if member.name.endswith("/PKG-INFO")]
if len(pkg_info_files) != 1:
raise SystemExit(f"Expected one PKG-INFO file, found {len(pkg_info_files)}")
file_obj = tf.extractfile(pkg_info_files[0])
if file_obj is None:
raise SystemExit("Failed to read PKG-INFO from sdist")
metadata_text = file_obj.read().decode("utf-8", errors="replace")
else:
raise SystemExit(f"Unsupported artifact type: {artifact_path}")
for line in metadata_text.splitlines():
if line.startswith("Version: "):
print(line.split(":", 1)[1].strip())
break
else:
raise SystemExit("Could not find Version header")
PY
)"
if [[ "$ARTIFACT_VERSION" != "$EXPECTED_VERSION" ]]; then
echo "ERROR: Artifact version mismatch for ${artifact}"
echo "Expected version: $EXPECTED_VERSION"
echo "Artifact version: $ARTIFACT_VERSION"
exit 1
fi
done
echo "Standalone artifact validation passed for version ${EXPECTED_VERSION}"
- name: Smoke test standalone package wheel install
run: |
set -euo pipefail
uv venv /tmp/modelaudit-picklescan-wheel-smoke
if [[ -x /tmp/modelaudit-picklescan-wheel-smoke/bin/python ]]; then
SMOKE_PYTHON=/tmp/modelaudit-picklescan-wheel-smoke/bin/python
else
SMOKE_PYTHON=/tmp/modelaudit-picklescan-wheel-smoke/Scripts/python.exe
fi
shopt -s nullglob
picklescan_wheels=(dist/modelaudit_picklescan-*.whl)
if [[ ${#picklescan_wheels[@]} -ne 1 ]]; then
echo "ERROR: Expected exactly 1 modelaudit_picklescan wheel artifact, found ${#picklescan_wheels[@]}"
ls -la dist/
exit 1
fi
uv pip install --python "$SMOKE_PYTHON" "${picklescan_wheels[0]}"
smoke_dir="$(mktemp -d)"
(
cd "$smoke_dir"
PYTHONPATH='' "$SMOKE_PYTHON" -I - <<'PY'
import importlib.util
import modelaudit_picklescan
assert importlib.util.find_spec("modelaudit") is None
assert importlib.util.find_spec("modelaudit_picklescan._rust") is not None
report = modelaudit_picklescan.scan_bytes(b"\x80\x04}q\x00.")
assert report.status.value == "complete", report
assert report.verdict.value == "clean", report
assert not any(notice.code == "engine_fallback" for notice in report.notices)
print("standalone modelaudit_picklescan wheel loaded without modelaudit")
PY
)
- name: Upload standalone package artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: modelaudit-picklescan-dist-${{ matrix.artifact-suffix }}
path: packages/modelaudit-picklescan/dist/
publish-pypi:
if: needs.release-please.outputs.release_created == 'true'
needs: [build, release-please]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/modelaudit/
permissions:
contents: read
id-token: write
steps:
- name: Download build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: dist
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
with:
print-hash: true
attestations: true
publish-picklescan-pypi:
if: needs.release-please.outputs.picklescan_release_created == 'true'
needs: [build-picklescan-package, release-please]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/modelaudit-picklescan/
permissions:
contents: read
id-token: write
steps:
- name: Download standalone pickle package artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: modelaudit-picklescan-dist-*
path: dist/
merge-multiple: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
with:
print-hash: true
attestations: true
provenance:
if: needs.release-please.outputs.release_created == 'true'
needs: [build, publish-pypi, release-please]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
sparse-checkout: |
pyproject.toml
uv.lock
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- name: Download build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: dist
path: dist/
- name: Generate artifact attestations
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4
with:
subject-path: "dist/*"
- name: Generate SBOM
run: |
uv export \
--frozen \
--no-dev \
--preview-features sbom-export \
--format cyclonedx1.5 \
--output-file dist/modelaudit-${{ needs.release-please.outputs.version }}.cdx.json \
> /dev/null
echo "SBOM generated:"
ls -la dist/*.cdx.json
# Upload wheel/sdist and SBOM to the GitHub Release as an alternative
# download location alongside PyPI. --clobber ensures idempotency on re-runs.
- name: Upload build artifacts to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ needs.release-please.outputs.tag_name }}" dist/* \
--repo "${{ github.repository }}" \
--clobber
picklescan-provenance:
if: needs.release-please.outputs.picklescan_release_created == 'true'
needs: [build-picklescan-package, publish-picklescan-pypi, release-please]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
sparse-checkout: |
packages/modelaudit-picklescan/pyproject.toml
packages/modelaudit-picklescan/uv.lock
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
enable-cache: true
- name: Download standalone pickle package artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: modelaudit-picklescan-dist-*
path: dist/
merge-multiple: true
- name: Generate artifact attestations
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4
with:
subject-path: "dist/*"
- name: Generate standalone package SBOM
working-directory: packages/modelaudit-picklescan
run: |
uv export \
--frozen \
--no-dev \
--preview-features sbom-export \
--format cyclonedx1.5 \
--output-file ../../dist/modelaudit-picklescan-${{ needs.release-please.outputs.picklescan_version }}.cdx.json \
> /dev/null
echo "Standalone package SBOM generated:"
ls -la ../../dist/*.cdx.json
- name: Upload standalone artifacts to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ needs.release-please.outputs.picklescan_tag_name }}" dist/* \
--repo "${{ github.repository }}" \
--clobber