Skip to content

feat(nightly): publish aiperf-nightly wheel alongside aiperf#914

Merged
saturley-hall merged 8 commits into
mainfrom
harrison/updates-for-nightly-versioning
May 18, 2026
Merged

feat(nightly): publish aiperf-nightly wheel alongside aiperf#914
saturley-hall merged 8 commits into
mainfrom
harrison/updates-for-nightly-versioning

Conversation

@saturley-hall

@saturley-hall saturley-hall commented May 11, 2026

Copy link
Copy Markdown
Member

Summary

  • Vendors a stdlib-only tools/rename_wheel.py utility that repacks a wheel under a new distribution name — rewriting METADATA, dist-info/, RECORD hashes, and the version("aiperf") literals inside aiperf/__init__.py so aiperf.__version__ keeps resolving under the renamed dist.
  • Updates .github/workflows/nightly.yml to produce aiperf_nightly-<dev-version>-py3-none-any.whl from the same source build as the canonical wheel, validate both wheels in separate venvs, upload both to S3, stage both to Artifactory, and forward both filenames to the GitLab security scan trigger.
  • Also includes the earlier refactor that aligns AggregateConfidenceJsonExporter with the rest of the codebase by using the shared aiperf.__version__ instead of a local importlib.metadata.version("aiperf") call (so rename_wheel.py's source-patching has one consistent literal to rewrite).

Why: users tracking development builds need pip install aiperf-nightly to work without colliding with the stable aiperf distribution name on PyPI/Artifactory. Two wheels from one source build keeps the container image unchanged (it still installs the canonical aiperf) while making the nightly index installable under its own name.

Closes OPS-5191.

Out-of-band coordination

The GitLab security-scan pipeline will need a follow-up to consume the new NIGHTLY_WHEEL_FILENAME form variable. Until then the canonical wheel continues to be scanned exactly as before; the renamed wheel is byte-equivalent except for metadata/RECORD, so source coverage is unchanged.

Test plan

  • pre-commit run --files tools/rename_wheel.py .github/workflows/nightly.yml — all green (the local validate-plugin-schemas failure is a pre-existing crick env issue, unrelated)
  • Local smoke test: uv build --wheelpython3 tools/rename_wheel.py dist/aiperf-*.whl --output-dir dist/ → patched 1 call in aiperf/__init__.py, produced aiperf_nightly-0.8.0-py3-none-any.whl
  • Local install + version check: both wheels installed into separate venvs, both report aiperf.__version__ == 0.8.0; importlib.metadata.version("aiperf-nightly") resolves correctly under the renamed dist
  • workflow_dispatch of nightly.yml with release=false on this branch to exercise build + validate + S3 only
  • workflow_dispatch with release=true to exercise full Artifactory staging; confirm both filenames appear at ${ARTIFACTORY_URL}/nightly/
  • pip install --extra-index-url ${ARTIFACTORY_PYPI_URL} aiperf-nightly resolves to the staged version end-to-end

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Nightly workflow now produces, validates, stages and uploads both canonical and nightly-variant wheel distributions, and exposes the nightly wheel filename for downstream steps.
  • New Features
    • Added a CLI utility to repackage/rename wheel files to create nightly wheel variants.
  • Bug Fixes
    • Aggregated JSON exports now include the package version reliably.
  • Tests
    • Updated tests to assert version and user-agent using the package's version.

Review Change Stack

saturley-hall and others added 2 commits May 11, 2026 16:38
…rter

AggregateConfidenceJsonExporter was calling importlib.metadata.version("aiperf")
directly with its own try/except, while every other exporter imports the
shared aiperf.__version__ symbol. Align this exporter with the convention
so version lookup happens in one place (src/aiperf/__init__.py).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Harrison King Saturley-Hall <hsaturleyhal@nvidia.com>
Users who track development builds need a `pip install aiperf-nightly`
target that doesn't collide with the stable `aiperf` distribution name
on PyPI/Artifactory. The nightly pipeline now produces both wheels from
a single source build: the canonical `aiperf-<dev-version>.whl` and a
renamed `aiperf_nightly-<dev-version>.whl` whose only differences are
the dist name in METADATA, the dist-info directory, RECORD hashes, and
the `version("aiperf")` literal inside `aiperf/__init__.py` (rewritten
in-place so `aiperf.__version__` still resolves under the new dist).

Changes:

* tools/rename_wheel.py (new): stdlib-only utility that repacks a wheel
  with a new distribution name. Handles METADATA `Name:` rewrite,
  dist-info directory rename, RECORD regeneration with fresh sha256s,
  PEP 491 filename construction, and source-patching of
  `version("aiperf")` / `get_version("aiperf")` /
  `importlib.metadata.version("aiperf")` calls so __version__ lookups
  keep working under the renamed dist. Lives in tools/ — outside the
  hatchling src/ root, so it is not bundled into the wheel.

* .github/workflows/nightly.yml:
  - prepare-nightly: emit a new `nightly_wheel_filename` output
    (`aiperf_nightly-<dev-version>-py3-none-any.whl`, PEP 491 escape).
    Stored in version-info.env alongside the canonical filename so
    retried runs restore it.
  - build-container: new "Generate aiperf-nightly wheel variant" step
    runs tools/rename_wheel.py against the canonical wheel after the
    buildx export.
  - build-container: validate step now installs each wheel into its own
    venv (.venv-check-orig and .venv-check-nightly) — the two dists
    share the `aiperf/` top-level module and cannot coexist. Both must
    report `aiperf.__version__ == EXPECTED_VERSION`, which also proves
    the rename script's source-patching survived the round trip.
  - build-container: S3 upload loops over `aiperf*.whl` (no hyphen) so
    both wheels are pushed to s3://${S3_BUCKET}/aiperf/nightly/<ver>/.
  - stage-nightly-wheel: pulls and Artifactory-deploys both filenames
    in a loop, keeping the existing single-object aws s3 cp pattern.
  - trigger-gitlab-security-scan: forwards both WHEEL_FILENAME and
    NIGHTLY_WHEEL_FILENAME as pipeline variables. The GitLab side will
    need a follow-up to consume the new variable; until then the
    canonical wheel continues to be scanned as before.

The container image is unchanged — it still ships the canonical
`aiperf` install. The renamed wheel is a side artifact.

Verified locally:
  uv build --wheel
  python3 tools/rename_wheel.py dist/aiperf-*.whl --output-dir dist/
  -> patched 1 call in aiperf/__init__.py
  -> both wheels install into separate venvs and report __version__ correctly
  -> importlib.metadata.version("aiperf-nightly") resolves under the renamed dist

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Harrison King Saturley-Hall <hsaturleyhal@nvidia.com>
@github-actions github-actions Bot added the feat label May 11, 2026
@github-actions

github-actions Bot commented May 11, 2026

Copy link
Copy Markdown

Try out this PR

Quick install:

pip install --upgrade --force-reinstall git+https://github.com/ai-dynamo/aiperf.git@9b19454f684b92c9a1dcae46752daec9f87e073d

Recommended with virtual environment (using uv):

uv venv --python 3.12 && source .venv/bin/activate
uv pip install --upgrade --force-reinstall git+https://github.com/ai-dynamo/aiperf.git@9b19454f684b92c9a1dcae46752daec9f87e073d

Last updated for commit: 9b19454Browse code

@saturley-hall saturley-hall requested review from ajcasagrande and pvijayakrish and removed request for pvijayakrish May 11, 2026 21:14
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cd4a6d84-f4cf-4618-a72f-95e3db53d03c

📥 Commits

Reviewing files that changed from the base of the PR and between 008358d and da5cd33.

📒 Files selected for processing (3)
  • .github/workflows/nightly.yml
  • tests/component_integration/cli/test_cli_help.py
  • tests/unit/transports/test_base_transport.py

Walkthrough

Adds a CLI to repack wheels with a new distribution name, computes and exposes a nightly wheel filename in the nightly workflow, generates/validates/uploads both canonical and nightly wheels, stages both to Artifactory and forwards filenames to GitLab scanning, and switches version lookups/tests to use aiperf.__version__.

Changes

Nightly Wheel Variant Build and Deployment

Layer / File(s) Summary
Wheel Rename Utility
tools/rename_wheel.py
New CLI tool extracts and repackages wheels with a new distribution name. Provides escape_name(), sha256_record(), patch_source_version_calls(), and main() orchestrating extraction, METADATA/.dist-info update, optional source patching, RECORD regeneration, and writing the new wheel.
Nightly Workflow Version Preparation
.github/workflows/nightly.yml
Compute NIGHTLY_WHEEL_FILENAME for the repacked variant, export job output nightly_wheel_filename, and persist NIGHTLY_WHEEL_FILENAME in version-info.env for reruns.
Build and Validate Both Wheels
.github/workflows/nightly.yml
AMD64 build phase generates aiperf-nightly via tools/rename_wheel.py, validates both canonical and nightly wheels by installing each into separate venvs and asserting aiperf.__version__, runs unit tests against the nightly wheel, and uploads all aiperf*.whl artifacts to S3.
Stage to Artifactory & GitLab Trigger
.github/workflows/nightly.yml
S3 → Artifactory stage deploys both canonical and nightly wheel filenames via a loop, failing on any non-201 response; GitLab security scan trigger payload and env now include both WHEEL_FILENAME and NIGHTLY_WHEEL_FILENAME.
Exporter and Test Version Import Updates
src/aiperf/exporters/aggregate/aggregate_confidence_json_exporter.py, tests/...
Exporter now imports aiperf.__version__ directly with TYPE_CHECKING-guarded typing and an explicit return annotation; CLI and transport tests updated to compare against aiperf.__version__.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


Poem

🐰 I hopped through wheels by moonlight bright,

Renamed a build to keep the night,
Two small wheels rolled from dist to S3,
Then staged and scanned on Artifactory,
A rabbit cheers — two wheels, one flight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(nightly): publish aiperf-nightly wheel alongside aiperf' directly and clearly describes the main change: adding publication of a nightly wheel variant while keeping the canonical wheel, which is the core objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/aiperf/exporters/aggregate/aggregate_confidence_json_exporter.py (1)

53-53: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add return type hint.

The function is missing a return type annotation. Based on the return statement at line 108, the return type should be JsonExportData.

📝 Proposed fix
-    def _aggregate_to_export_data(self):
+    def _aggregate_to_export_data(self) -> "JsonExportData":
         """Convert AggregateResult to JsonExportData format.

Note: Use forward reference (quoted string) since JsonExportData is imported inside the method.

As per coding guidelines: "Type hints on ALL functions (params and return)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiperf/exporters/aggregate/aggregate_confidence_json_exporter.py` at line
53, The method _aggregate_to_export_data is missing a return type annotation;
update its signature to include the return type "JsonExportData" (use a forward
reference string since JsonExportData is imported inside the method), i.e.
change def _aggregate_to_export_data(self): to def
_aggregate_to_export_data(self) -> "JsonExportData": and keep the existing
return value unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/rename_wheel.py`:
- Around line 49-54: The function patch_source_version_calls is missing a return
type annotation; update its signature to include the return type dict[str, int]
(i.e., annotate the function as returning dict[str, int]) so the signature reads
patch_source_version_calls(... ) -> dict[str, int]: ensuring the rest of the
function body and return value remain unchanged.

---

Outside diff comments:
In `@src/aiperf/exporters/aggregate/aggregate_confidence_json_exporter.py`:
- Line 53: The method _aggregate_to_export_data is missing a return type
annotation; update its signature to include the return type "JsonExportData"
(use a forward reference string since JsonExportData is imported inside the
method), i.e. change def _aggregate_to_export_data(self): to def
_aggregate_to_export_data(self) -> "JsonExportData": and keep the existing
return value unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a9e7cbc-e8ea-4953-8cee-2613516ff043

📥 Commits

Reviewing files that changed from the base of the PR and between 79de74e and c246e77.

📒 Files selected for processing (3)
  • .github/workflows/nightly.yml
  • src/aiperf/exporters/aggregate/aggregate_confidence_json_exporter.py
  • tools/rename_wheel.py
Comment thread tools/rename_wheel.py Outdated
@codecov

codecov Bot commented May 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread tools/rename_wheel.py

@pvijayakrish pvijayakrish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are we not using this approach? wheel unpack -> modify metadata safely -> wheel pack

saturley-hall and others added 4 commits May 11, 2026 19:09
CodeRabbit flagged the function's bare `dict` return annotation. The
function returns `{"files": int, "replacements": int}`, so the more
precise hint is `dict[str, int]`.

Review: #914 (review)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Harrison King Saturley-Hall <hsaturleyhal@nvidia.com>
CodeRabbit flagged the missing return annotation. Since the runtime
import of JsonExportData is deferred inside the method body, the symbol
is exposed at module scope under TYPE_CHECKING so static analysis can
resolve the annotation without forcing the runtime import. The
annotation itself remains a quoted forward reference per CodeRabbit's
suggestion.

Aligns with the project standard "Type hints on ALL functions (params
and return)" from CLAUDE.md.

Review: #914 (review)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Harrison King Saturley-Hall <hsaturleyhal@nvidia.com>
Adds a deeper validation step to the nightly pipeline: install the
renamed `aiperf-nightly` wheel into a fresh venv and run the unit test
suite (`tests/unit/`) against it. Catches wheel-only failure modes the
existing source-tree `unit-tests` job cannot see — missing
Requires-Dist, a module that did not ship in the wheel, or shipped
code that accidentally imports from a dev-only test helper.

The new step lives inline in the `build-container` matrix job between
`Validate wheels (amd64 only)` and `Upload wheels to S3 (amd64 only)`,
amd64-only (the wheel is pure-Python). Failure is hard — it fails
build-container, which blocks `create-multiarch-manifest-ecr` and the
two staging jobs (`stage-nightly-wheel`, `stage-nightly-container`) via
their existing `needs:` chain. The existing source-tree `unit-tests`
job stays advisory exactly as before; this gate sits inside the
publish path, not the test path.

The step:
  1. Creates a fresh venv (`.venv-wheel-tests`).
  2. Installs the renamed `aiperf_nightly-*.whl`.
  3. Installs pytest + the dev-only test deps the wheel does not carry
     (pytest-asyncio, pytest-xdist, hypothesis, looptime, trustme).
     looptime in particular is non-optional: it powers the
     "asyncio.sleep runs instantly" auto-fixture in tests/conftest.py
     and ~12 timing tests fail without it. trustme is used by the
     TLS-cert fixtures in transport tests.
  4. Installs the in-tree `tests/aiperf_mock_server` editable so
     `tests/unit/conftest.py` can load — it imports `tests.harness`
     which transitively pulls in `aiperf_mock_server` even though
     unit tests themselves do not exercise the mock server.
  5. Runs `pytest tests/unit -n auto -m 'not performance and not stress'
     --tb=short` against the installed wheel. The src/ directory is
     not on sys.path, so `import aiperf` resolves to the installed
     wheel rather than the source tree.

Also fixes `tests/unit/transports/test_base_transport.py` which
hardcoded `importlib.metadata.version('aiperf')` at module scope.
That literal raises `PackageNotFoundError` under the renamed
`aiperf-nightly` distribution, cascading to ImportErrors in the four
transport test modules and in `test_imports.py`. Replaced with
`from aiperf import __version__ as aiperf_version`, matching the
pattern used by every other consumer of the version string in the
codebase (and consistent with the CodeRabbit-flagged fix to
aggregate_confidence_json_exporter.py earlier in this PR).

Verified locally:
  uv build --wheel
  python3 tools/rename_wheel.py dist/aiperf-*.whl --output-dir dist/
  uv venv .venv-wheel-tests
  uv pip install ... aiperf_nightly-*.whl
  uv pip install pytest pytest-asyncio pytest-xdist hypothesis looptime trustme
  uv pip install -e tests/aiperf_mock_server
  pytest tests/unit -n auto -m 'not performance and not stress'
  -> 9329 passed, 10 skipped, 0 failed, 87s (exit 0)

Known follow-up: `tests/component_integration/cli/test_cli_help.py:59`
has the same `get_package_version("aiperf")` hardcode. Out of scope for
this PR (component_integration is not in the nightly wheel-install
gate); will need fixing if/when that tier is added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Harrison King Saturley-Hall <hsaturleyhal@nvidia.com>
`tests/component_integration/cli/test_cli_help.py::TestCLIVersion::test_version_flag`
hardcoded `importlib.metadata.version("aiperf")` to compute the expected
version string. That literal raises `PackageNotFoundError` against the
renamed `aiperf-nightly` distribution, so the test cannot run if the
nightly wheel-install gate ever expands to cover component_integration.

Replace with `from aiperf import __version__ as aiperf_version` and
compare against that, matching the pattern already used by every other
consumer of the version in the codebase — including the same fix
applied to tests/unit/transports/test_base_transport.py in the prior
commit on this branch.

Pure renaming; the assertion is unchanged in spirit ("CLI's reported
version matches what the installed package declares"). On the source
tree both lookups return the same string; against the renamed wheel,
`aiperf.__version__` works because rename_wheel.py rewrites the
in-wheel `version("aiperf")` literal to `version("aiperf-nightly")`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Harrison King Saturley-Hall <hsaturleyhal@nvidia.com>
@saturley-hall saturley-hall requested a review from debermudez May 12, 2026 14:32
@saturley-hall

saturley-hall commented May 12, 2026

Copy link
Copy Markdown
Member Author

Why are we not using this approach? wheel unpack -> modify metadata safely -> wheel pack

Yes, in roughly descending importance:

  1. The wheel package doesn't actually have a "rename the distribution" operation. Its CLI is wheel pack / wheel unpack / wheel tags. wheel tags changes the py/abi/platform tags (e.g. py3-none-any → cp310-cp310-manylinux2014_x86_64), not the distribution name (aiperf → aiperf-nightly). The companion script retag_wheel.sh over in ~/dev/aiperf-nightly/ is exactly that — a thin wrapper around python -m wheel tags. Renaming the dist still requires unzip → edit METADATA[Name:] → rename the .dist-info/ dir → regen RECORD → zip back up, which is the bulk of what rename_wheel.py does already. Using wheel pack/unpack would shave maybe ~30 lines and add a dependency.

  2. The source-patching has no equivalent in the wheel package. The whole reason we don't just bump METADATA and call it a day is the patch_source_version_calls() function (tools/rename_wheel.py:49–99) — it rewrites version("aiperf") → version("aiperf-nightly") literals inside the wheel's .py files (notably aiperf/init.py:8) so aiperf.version keeps resolving after the rename. That's a custom pass on the unpacked tree, regardless of who does the (un)zipping around it.

  3. Zero install footprint matters in this CI step. The rename runs in the build-container job before any venv is set up — straight python3 tools/rename_wheel.py "${ORIG}" on the runner's system Python. With stdlib only, no uv pip install wheel step is needed; the script just runs. If we depended on the wheel package, we'd need either an extra install line in the workflow, or it'd have to run inside one of the validation venvs (which complicates ordering: we want the renamed wheel to exist before any validation venv is built around it).

If we ever decide to retag (change py3-none-any to something more specific) in addition to renaming, the right move would be to compose python -m wheel tags after rename_wheel.py, using each tool for its actual purpose, rather than fold tagging into our script

Comment thread .github/workflows/nightly.yml
The wheel-test venv only carried looptime/trustme on top of pytest, but
tests/unit/api and tests/unit/server import Starlette/FastAPI TestClient
and httpx.AsyncClient at collection time. Without httpx in the venv the
new nightly wheel gate would fail during pytest collection in a fresh
environment.

Signed-off-by: Harrison King Saturley-Hall <hsaturleyhal@nvidia.com>
@saturley-hall saturley-hall enabled auto-merge (squash) May 15, 2026 17:44
@saturley-hall saturley-hall requested a review from dynamo-ops May 18, 2026 01:31
@saturley-hall saturley-hall merged commit 03c252f into main May 18, 2026
18 of 23 checks passed
@saturley-hall saturley-hall deleted the harrison/updates-for-nightly-versioning branch May 18, 2026 01:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 participants