Skip to content

refactor: introduce WinMLManifest dataclass as single source of truth for build_manifest.json#1057

Open
xieofxie wants to merge 19 commits into
mainfrom
hualxie/winmlcli_manifest
Open

refactor: introduce WinMLManifest dataclass as single source of truth for build_manifest.json#1057
xieofxie wants to merge 19 commits into
mainfrom
hualxie/winmlcli_manifest

Conversation

@xieofxie

@xieofxie xieofxie commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces a WinMLManifest dataclass as the single source of truth for the build_manifest.json schema. This is a pure refactor — no new behavior, no filename changes.

Changes

New: WinMLManifest / ManifestStage dataclasses (utils/manifest.py)

  • Central schema for build_manifest.json — all producers construct a WinMLManifest and call .save()
  • to_dict() drops None fields for compact JSON; from_dict() is forward-compatible (unknown keys preserved in extras at both top-level and stage-level)
  • _sanitize_value() converts Path objects to strings and numpy scalars to native Python types, preserving numeric precision
  • find(), load(), manifest_path_for() helpers for consumers to adopt incrementally

Build producers updated to use WinMLManifest

  • build/hf.py: raw dict → WinMLManifest + ManifestStage
  • build/onnx.py: same refactor

Tests

  • 16 tests in tests/unit/utils/test_manifest.py (round-trip, None-omission, forward-compat extras at top and stage level, I/O, find, Path sanitization, numpy scalar sanitization)

Cleanup

  • Removed dead _cleanup_partial_composite function from export.py (defined but never called)

@xieofxie xieofxie requested a review from a team as a code owner July 6, 2026 06:55
Comment thread tests/unit/utils/test_manifest.py Fixed

@DingmaomaoBJTU DingmaomaoBJTU 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.

Solid, well-structured refactor — the WinMLManifest/ManifestStage dataclasses are clean, well-documented, forward-compatible (extras), and have a dedicated test_manifest.py. A few issues though, most notably the rename is incomplete.

🔴 Bugs / correctness

1. Missed rename in utils/cli.py — the rename is not complete

classify_model_input (src/winml/modelkit/utils/cli.py:91-92) still globs the old name:

manifest_files = list(path.glob("build_manifest.json")) + list(path.glob("*_build_manifest.json"))

Producers now write winml_manifest.json, so a folder produced by the new winml build/export yields is_winml_cli_folder=False. It's currently latent (no command reads is_winml_cli_folder/folder_has_onnx — only tests/unit/utils/test_cli.py does), so the paired tests (~L447-474) still pass while validating stale behavior. This contradicts the PR's "all references updated (15 files)" claim. Please update the globs and those tests.

2. Dead code: _cleanup_partial_composite (commands/export.py:67)

Defined but never called. The composite-failure path (export.py:595) deliberately calls _warn_partial_composite, and its docstring states "we do NOT delete anything." So this helper is unreachable — either wire it in intentionally or remove it.

🟠 Coverage / design

3. New "export writes manifest" behavior is untested

commands/export.py now writes winml_manifest.json (single) and <stem>_winml_manifest.json (composite), but tests/unit/commands/test_export.py has no manifest assertions. The hf/onnx manifest-writing paths are tested; export's new path isn't. Please add coverage for both the single and composite-prefixed cases.

4. No fallback for pre-existing build_manifest.json caches

inference/engine.py, inspect/resolver.py, and serve/app.py now look only for winml_manifest.json (no fallback). Combined with #1, the codebase splits: cli.py recognizes only the old name while everything else recognizes only the new name — so neither pre-existing caches nor new outputs are recognized consistently across all components. If caches are considered disposable/pre-release this may be acceptable, but it deserves an explicit decision (or a one-time fallback read of the old filename).

🟡 Nits

  • save() uses json.dumps(..., default=str) (utils/manifest.py), which the old writer didn't. If export_stats/analyze_details ever contain numpy scalars, numeric metrics silently serialize as strings ("10" rather than 10). Consider sanitizing the payload instead of a blanket default=str.
  • Forward-compat asymmetry: top-level unknown keys survive in extras, but unknown keys inside a stage are dropped by from_dict (filtered against ManifestStage.__dataclass_fields__). Minor, but the "forward-compatible" guarantee doesn't hold for stage entries.
  • Import convention: producers use function-level from ..utils.manifest import ... (reaching into the submodule) even though both symbols are exported by utils/__init__.py; per the repo convention prefer from ..utils import ManifestStage, WinMLManifest.

- Fix build_manifest.json -> winml_manifest.json rename missed in utils/cli.py
- Update corresponding test assertions in test_cli.py
- Remove dead _cleanup_partial_composite (defined but never called)
- Fix import convention: use 'from ..utils import' not 'from ..utils.manifest import'
- Replace blanket default=str with _sanitize_value to preserve numeric types
- Add ManifestStage.extras for forward-compatible stage fields
- Add tests: export manifest (single + composite), Path sanitization, stage extras
Comment thread src/winml/modelkit/inference/engine.py Outdated
The _sanitize_value handles known types (Path -> str) explicitly to
preserve numerics, but default=str remains as a fallback for unexpected
types (e.g. MagicMock in tests) to avoid serialization crashes.

@DingmaomaoBJTU DingmaomaoBJTU 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.

Nice refactor — WinMLManifest as a single source of truth is a clear improvement and the tests are thorough. Two things worth a look inline before merge.

Comment thread src/winml/modelkit/inspect/resolver.py Outdated
Comment thread src/winml/modelkit/utils/manifest.py
@xieofxie

Copy link
Copy Markdown
Contributor Author

need to rethink about how to detect if the model is generated by winml cli

Hualiang Xie added 2 commits July 13, 2026 11:41
… for export

Revert winml_manifest.json rename back to build_manifest.json for build
outputs. Export outputs now use export_manifest.json instead.

- Split MANIFEST_FILENAME into BUILD/EXPORT constants
- manifest_path_for() accepts filename parameter
- find() discovers both build and export manifests
- Consumers (engine, resolver, serve) updated for both patterns
- All tests and docs updated
@xieofxie xieofxie changed the title refactor: introduce WinMLManifest class and rename to winml_manifest.json refactor: introduce WinMLManifest class, add export manifest, unify manifest handling Jul 13, 2026
Add np.generic -> .item() conversion so numpy floats/ints/bools in
export_stats or analyze_details serialize as native Python types
instead of falling through to default=str.
Comment thread src/winml/modelkit/utils/manifest.py Fixed

@DingmaomaoBJTU DingmaomaoBJTU 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.

Review — WinMLManifest refactor

Overall a clean, well-tested refactor: the naming split (build_manifest.json for build, export_manifest.json for export), the export-side manifest generation, and the composite prefix logic all look correct, and the earlier threads (numpy sanitization, filename revert, backward-compat glob) appear addressed.

Main structural feedback — the "single source of truth" is only half-wired: the write side goes through WinMLManifest.save(), but the read side (find()/load()/from_dict()) has no production callers and consumers still hand-parse JSON with duplicated filename literals. Details inline; nothing here is blocking.

🤖 Reviewed with GitHub Copilot CLI

Comment thread src/winml/modelkit/utils/manifest.py
Comment thread src/winml/modelkit/inspect/resolver.py Outdated
Comment thread src/winml/modelkit/commands/export.py Outdated
Revert export.py to main (no manifest generation in export).
Remove EXPORT_MANIFEST_FILENAME, export manifest tests, and all
export_manifest references. Export manifest generation deferred
to issue #1038.
@xieofxie xieofxie changed the title refactor: introduce WinMLManifest class, add export manifest, unify manifest handling refactor: introduce WinMLManifest dataclass as single source of truth for build_manifest.json Jul 13, 2026
- inference/engine.py: import MANIFEST_FILENAME + WinMLManifest,
  replace string literals with constant, use WinMLManifest.find()
  in _resolve_model_id_from_dir
- inspect/resolver.py: use MANIFEST_FILENAME glob + WinMLManifest.load()
  with typed ManifestStage attribute access
- serve/app.py: use MANIFEST_FILENAME + WinMLManifest.load().to_dict()
- build/hf.py, build/onnx.py: use MANIFEST_FILENAME constant for path

No string literal 'build_manifest.json' remains in production source.
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.

4 participants