fix: validate HF wheel licenses before upload - #2386
Conversation
Signed-off-by: Charles Blackmon-Luca <20627856+charlesbluca@users.noreply.github.com>
2d1aabd to
7df9f7a
Compare
Greptile SummaryThis PR moves Hugging Face wheel license validation from an OCR-specific post-build check in the workflow YAML into the shared
|
| Filename | Overview |
|---|---|
| ci/scripts/nightly_build_publish.py | Adds _validate_wheel_license_metadata (pre-upload, every wheel) and updates _patch_pyproject_license to detect setuptools backends and emit legacy {text = …} form; logic is correct and mirrors the adjacent _validate_required_wheel_members pattern. |
| .github/workflows/huggingface-nightly.yml | Removes the OCR-only post-build license check now handled by the shared builder; OCR-specific version, dependency, and extension-ABI checks remain untouched. |
| ci/tests/test_huggingface_release_workflow.py | Adds 7 new test functions covering the full matrix of license header combinations, every-wheel iteration, classifier absence, and ordering of validation before upload; SPDX header present, all tests are unit-level with no external calls. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant W as Workflow YAML
participant B as nightly_build_publish main
participant V as _validate_wheel_license_metadata
participant T as _twine_upload
W->>B: run with --license-text Apache-2.0
B->>B: _patch_pyproject_license (setuptools uses legacy form)
B->>B: "_build produces dist/*.whl"
B->>B: _validate_required_wheel_members
B->>V: _validate_wheel_license_metadata(dist_dir)
loop "every *.whl"
V->>V: open ZipFile, read .dist-info/METADATA
V->>V: prefer License-Expression, fallback to License header
V->>V: check Trove classifier present
end
alt any failure
V-->>B: raise RuntimeError, abort before upload
else all pass
V-->>B: return
B->>T: _twine_upload if args.upload
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant W as Workflow YAML
participant B as nightly_build_publish main
participant V as _validate_wheel_license_metadata
participant T as _twine_upload
W->>B: run with --license-text Apache-2.0
B->>B: _patch_pyproject_license (setuptools uses legacy form)
B->>B: "_build produces dist/*.whl"
B->>B: _validate_required_wheel_members
B->>V: _validate_wheel_license_metadata(dist_dir)
loop "every *.whl"
V->>V: open ZipFile, read .dist-info/METADATA
V->>V: prefer License-Expression, fallback to License header
V->>V: check Trove classifier present
end
alt any failure
V-->>B: raise RuntimeError, abort before upload
else all pass
V-->>B: return
B->>T: _twine_upload if args.upload
end
Reviews (1): Last reviewed commit: "fix: validate HF wheel licenses before u..." | Re-trigger Greptile
Description
Hugging Face package builds now validate Apache-2.0 wheel metadata before any PyPI upload. The shared builder inspects every wheel's single
.dist-info/METADATA, prefers the PEP 639License-Expressionheader, falls back to the legacyLicenseheader, and requires the configured Apache classifier. Failures include the wheel filename and both observed license fields.The previous OCR-only workflow check ran after upload and read only the legacy
Licenseheader. Hatchling emitsLicense-Expression: Apache-2.0, so a valid OCR wheel could be reported as invalid only after publication. Moving validation into the shared builder protects OCR, page-elements, and table-structure before_twine_upload; OCR-specific version, dependency, package-content, extension-ABI, and platform-tag checks remain in the workflow.Current setuptools rejects combining a PEP 639 license expression with the required Apache Trove classifier. The metadata patch therefore keeps modern SPDX expressions for hatchling packages and uses the equivalent legacy text form for setuptools packages. Both forms are enforced by the shared validator and continue to bundle the Apache license file.
OCRv2 2.0.2 was already fully published and must not be rerun.
Validation
uv run --with pytest python -m pytest ci/tests/test_huggingface_release_workflow.py -q— 34 passed.uvx pre-commit run --files ci/scripts/nightly_build_publish.py ci/tests/test_huggingface_release_workflow.py .github/workflows/huggingface-nightly.yml— all hooks passed.git diff --check— passed.nemotron-page-elements-v33.0.2.dev20260721152500,py3-none-any:License='Apache-2.0', noLicense-Expression, Apache classifier present; validator andtwine checkpassed.nemotron-table-structure-v11.0.1.dev20260721152500,py3-none-any:License='Apache-2.0', noLicense-Expression, Apache classifier present; validator andtwine checkpassed.nemotron-ocr2.0.2.dev20260721152500, CPython 3.12manylinux_2_34_x86_64, built innvidia/cuda:13.0.0-devel-ubuntu24.04with the workflow build, runtime-pin, auditwheel, and required-member flags:License-Expression='Apache-2.0', no legacyLicense, Apache classifier present; validator andtwine checkpassed. Twine reported only the package's existing missing long-description warnings.nemotron-ocr2.0.2CPython 3.12manylinux_2_34_aarch64: the same production validator passed; independent inspection foundLicense-Expression='Apache-2.0', no legacyLicense, and the Apache classifier.twine checkpassed with the same existing long-description warnings.Checklist