Publish Tiny Hinglish Turn Detector development preview
Browse filesrelease inventory sha256: fa73d6c3defbd256e431aa99c8b4fae4ea6e74cbb2fb736f57eb43fbbe65f41a
- MODEL_CARD.md +1 -1
- README.md +1 -1
- REPORT.md +2 -2
- SOURCE_README.md +8 -1
- export_manifest.json +3 -3
- release_provenance.json +12 -12
- scripts/publish_hf.py +24 -0
- tests/test_publish_hf.py +67 -0
MODEL_CARD.md
CHANGED
|
@@ -303,7 +303,7 @@ failure aggregates, silence perturbation report, exact environment files, CPU
|
|
| 303 |
benchmarks, and guarded release tooling. Per-example validation predictions and
|
| 304 |
the text-bearing split manifest remain in the private local workspace; the
|
| 305 |
public package contains aggregate reports and hashes, not those sensitive rows.
|
| 306 |
-
The quality gate completed with Ruff 0.15.16 passing and
|
| 307 |
passing via Python `unittest` discovery. The configured Hugging Face owner is
|
| 308 |
`suvradeepp`.
|
| 309 |
|
|
|
|
| 303 |
benchmarks, and guarded release tooling. Per-example validation predictions and
|
| 304 |
the text-bearing split manifest remain in the private local workspace; the
|
| 305 |
public package contains aggregate reports and hashes, not those sensitive rows.
|
| 306 |
+
The quality gate completed with Ruff 0.15.16 passing and 130/130 automated tests
|
| 307 |
passing via Python `unittest` discovery. The configured Hugging Face owner is
|
| 308 |
`suvradeepp`.
|
| 309 |
|
README.md
CHANGED
|
@@ -303,7 +303,7 @@ failure aggregates, silence perturbation report, exact environment files, CPU
|
|
| 303 |
benchmarks, and guarded release tooling. Per-example validation predictions and
|
| 304 |
the text-bearing split manifest remain in the private local workspace; the
|
| 305 |
public package contains aggregate reports and hashes, not those sensitive rows.
|
| 306 |
-
The quality gate completed with Ruff 0.15.16 passing and
|
| 307 |
passing via Python `unittest` discovery. The configured Hugging Face owner is
|
| 308 |
`suvradeepp`.
|
| 309 |
|
|
|
|
| 303 |
benchmarks, and guarded release tooling. Per-example validation predictions and
|
| 304 |
the text-bearing split manifest remain in the private local workspace; the
|
| 305 |
public package contains aggregate reports and hashes, not those sensitive rows.
|
| 306 |
+
The quality gate completed with Ruff 0.15.16 passing and 130/130 automated tests
|
| 307 |
passing via Python `unittest` discovery. The configured Hugging Face owner is
|
| 308 |
`suvradeepp`.
|
| 309 |
|
REPORT.md
CHANGED
|
@@ -525,8 +525,8 @@ calendar days or the missing external data collection occurred.
|
|
| 525 |
| 13 | Integrate controller and demo | replay, three-state controller, Gradio, Hinglish recording prompts | Preview model and synthetic controller integration complete; real sequence replay pending |
|
| 526 |
| 14 | Freeze test, package, publish | sealed-test guard, atomic release builder, exact-mirror publisher | Development packaging path ready; official-test/final gates not satisfied |
|
| 527 |
|
| 528 |
-
The repository quality gate passed Ruff 0.15.16 and all
|
| 529 |
-
(
|
| 530 |
the missing empirical datasets or rights review.
|
| 531 |
|
| 532 |
## 9. Decision log and next experiments
|
|
|
|
| 525 |
| 13 | Integrate controller and demo | replay, three-state controller, Gradio, Hinglish recording prompts | Preview model and synthetic controller integration complete; real sequence replay pending |
|
| 526 |
| 14 | Freeze test, package, publish | sealed-test guard, atomic release builder, exact-mirror publisher | Development packaging path ready; official-test/final gates not satisfied |
|
| 527 |
|
| 528 |
+
The repository quality gate passed Ruff 0.15.16 and all 130 automated tests
|
| 529 |
+
(130 passed via Python `unittest` discovery). That validates the implemented behavior under test; it does not fill
|
| 530 |
the missing empirical datasets or rights review.
|
| 531 |
|
| 532 |
## 9. Decision log and next experiments
|
SOURCE_README.md
CHANGED
|
@@ -26,7 +26,7 @@ engineering evidence, not a production-readiness claim.
|
|
| 26 |
| Domain-shift stress | Measured, confounded | Source-held-out AP 0.5500/0.5368 for TinyTCN/baseline; both near chance in ranking, with wide nine-source bootstrap intervals |
|
| 27 |
| Runtime/export | Measured | 611 KiB FP32 ONNX; exact PyTorch/ONNX parity; ONNX p95 0.956 ms neural-only and 1.629 ms waveform-to-probability |
|
| 28 |
| Controller integration | Synthetic only | Metadata-bound 8-checkpoint/3-turn fixture emits 3 response edges with 0 duplicates; not model or product-quality evidence |
|
| 29 |
-
| Code quality gate | Passed | Ruff 0.15.16;
|
| 30 |
| Accuracy/generalization | **Not established** | No full-corpus run, collected Hinglish recordings, real conversation replay, or official-test evaluation |
|
| 31 |
| Hugging Face publication | Not claimed here | Local guarded release/publisher path exists; verify any remote publication separately |
|
| 32 |
|
|
@@ -321,6 +321,13 @@ uv run python scripts/publish_hf.py --username suvradeepp
|
|
| 321 |
uv run python scripts/publish_hf.py --username suvradeepp --execute
|
| 322 |
```
|
| 323 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
The release builder refuses smoke checkpoints or a release without measured
|
| 325 |
test metrics unless `--allow-development-artifact` is passed explicitly. That
|
| 326 |
override must remain visibly development-only. For a reviewable partial-data
|
|
|
|
| 26 |
| Domain-shift stress | Measured, confounded | Source-held-out AP 0.5500/0.5368 for TinyTCN/baseline; both near chance in ranking, with wide nine-source bootstrap intervals |
|
| 27 |
| Runtime/export | Measured | 611 KiB FP32 ONNX; exact PyTorch/ONNX parity; ONNX p95 0.956 ms neural-only and 1.629 ms waveform-to-probability |
|
| 28 |
| Controller integration | Synthetic only | Metadata-bound 8-checkpoint/3-turn fixture emits 3 response edges with 0 duplicates; not model or product-quality evidence |
|
| 29 |
+
| Code quality gate | Passed | Ruff 0.15.16; 130/130 automated tests passed via `unittest` discovery |
|
| 30 |
| Accuracy/generalization | **Not established** | No full-corpus run, collected Hinglish recordings, real conversation replay, or official-test evaluation |
|
| 31 |
| Hugging Face publication | Not claimed here | Local guarded release/publisher path exists; verify any remote publication separately |
|
| 32 |
|
|
|
|
| 321 |
uv run python scripts/publish_hf.py --username suvradeepp --execute
|
| 322 |
```
|
| 323 |
|
| 324 |
+
For a personal account without Hugging Face PRO, the publisher requests the
|
| 325 |
+
free-account-compatible ZeroGPU tier (`--space-hardware zero-a10g`) only when it
|
| 326 |
+
must create a new Space. Hugging Face currently gates new personal Gradio CPU
|
| 327 |
+
Spaces behind PRO even when CPU Basic has no hourly charge. ZeroGPU eligibility
|
| 328 |
+
still requires a verified account in good standing and an available hosted
|
| 329 |
+
Space slot. Paid accounts may override this with `--space-hardware cpu-basic`.
|
| 330 |
+
|
| 331 |
The release builder refuses smoke checkpoints or a release without measured
|
| 332 |
test metrics unless `--allow-development-artifact` is passed explicitly. That
|
| 333 |
override must remain visibly development-only. For a reviewable partial-data
|
export_manifest.json
CHANGED
|
@@ -171,9 +171,9 @@
|
|
| 171 |
"sha256": "89167b269b2103ad0bd94918f2ec4f7b605cf001d391e570683ceef39066b6c2"
|
| 172 |
},
|
| 173 |
{
|
| 174 |
-
"bytes":
|
| 175 |
"path": "scripts/publish_hf.py",
|
| 176 |
-
"sha256": "
|
| 177 |
},
|
| 178 |
{
|
| 179 |
"bytes": 3752,
|
|
@@ -371,7 +371,7 @@
|
|
| 371 |
"sha256": "da0eac829fe43615f7bb149998c2f2762f148790fb0c14d4ba6db4d6b25298f4"
|
| 372 |
}
|
| 373 |
],
|
| 374 |
-
"source_inventory_sha256": "
|
| 375 |
"task": "audio-turn-end-detection",
|
| 376 |
"threshold": 0.741000771522522,
|
| 377 |
"training_data": {
|
|
|
|
| 171 |
"sha256": "89167b269b2103ad0bd94918f2ec4f7b605cf001d391e570683ceef39066b6c2"
|
| 172 |
},
|
| 173 |
{
|
| 174 |
+
"bytes": 43530,
|
| 175 |
"path": "scripts/publish_hf.py",
|
| 176 |
+
"sha256": "36311a2457bb0e93cbd6fd0ed444e57a2ec2c44ab9bd17ed06132e9640a9ddc3"
|
| 177 |
},
|
| 178 |
{
|
| 179 |
"bytes": 3752,
|
|
|
|
| 371 |
"sha256": "da0eac829fe43615f7bb149998c2f2762f148790fb0c14d4ba6db4d6b25298f4"
|
| 372 |
}
|
| 373 |
],
|
| 374 |
+
"source_inventory_sha256": "259931c509dc4abd54a3c5c72ca12948449bd19d3aed2b2c2ea0fac88a6a705b",
|
| 375 |
"task": "audio-turn-end-detection",
|
| 376 |
"threshold": 0.741000771522522,
|
| 377 |
"training_data": {
|
release_provenance.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
| 16 |
},
|
| 17 |
"MODEL_CARD.md": {
|
| 18 |
"bytes": 14805,
|
| 19 |
-
"sha256": "
|
| 20 |
},
|
| 21 |
"Makefile": {
|
| 22 |
"bytes": 777,
|
|
@@ -28,15 +28,15 @@
|
|
| 28 |
},
|
| 29 |
"README.md": {
|
| 30 |
"bytes": 14805,
|
| 31 |
-
"sha256": "
|
| 32 |
},
|
| 33 |
"REPORT.md": {
|
| 34 |
"bytes": 32272,
|
| 35 |
-
"sha256": "
|
| 36 |
},
|
| 37 |
"SOURCE_README.md": {
|
| 38 |
-
"bytes":
|
| 39 |
-
"sha256": "
|
| 40 |
},
|
| 41 |
"app.py": {
|
| 42 |
"bytes": 10381,
|
|
@@ -108,7 +108,7 @@
|
|
| 108 |
},
|
| 109 |
"export_manifest.json": {
|
| 110 |
"bytes": 12430,
|
| 111 |
-
"sha256": "
|
| 112 |
},
|
| 113 |
"model.onnx": {
|
| 114 |
"bytes": 625431,
|
|
@@ -283,8 +283,8 @@
|
|
| 283 |
"sha256": "89167b269b2103ad0bd94918f2ec4f7b605cf001d391e570683ceef39066b6c2"
|
| 284 |
},
|
| 285 |
"scripts/publish_hf.py": {
|
| 286 |
-
"bytes":
|
| 287 |
-
"sha256": "
|
| 288 |
},
|
| 289 |
"scripts/replay_stream.py": {
|
| 290 |
"bytes": 3752,
|
|
@@ -527,8 +527,8 @@
|
|
| 527 |
"sha256": "12a9a0edda07265de293076d12073a707b10e6fb9ebbf2e7c70ba9a219289bdc"
|
| 528 |
},
|
| 529 |
"tests/test_publish_hf.py": {
|
| 530 |
-
"bytes":
|
| 531 |
-
"sha256": "
|
| 532 |
},
|
| 533 |
"tests/test_release_guardrails.py": {
|
| 534 |
"bytes": 17399,
|
|
@@ -566,7 +566,7 @@
|
|
| 566 |
},
|
| 567 |
"MODEL_CARD.md": {
|
| 568 |
"bytes": 14805,
|
| 569 |
-
"sha256": "
|
| 570 |
},
|
| 571 |
"NOTICE": {
|
| 572 |
"bytes": 563,
|
|
@@ -626,5 +626,5 @@
|
|
| 626 |
"metrics_file": "development_metrics.json",
|
| 627 |
"metrics_scope": "development",
|
| 628 |
"model_file": "model.onnx",
|
| 629 |
-
"source_and_artifact_inventory_sha256": "
|
| 630 |
}
|
|
|
|
| 16 |
},
|
| 17 |
"MODEL_CARD.md": {
|
| 18 |
"bytes": 14805,
|
| 19 |
+
"sha256": "6add0853cdc0f09ff1abb594149c59e9d53f29ab4dba5de52bed7278cfb13c1b"
|
| 20 |
},
|
| 21 |
"Makefile": {
|
| 22 |
"bytes": 777,
|
|
|
|
| 28 |
},
|
| 29 |
"README.md": {
|
| 30 |
"bytes": 14805,
|
| 31 |
+
"sha256": "6add0853cdc0f09ff1abb594149c59e9d53f29ab4dba5de52bed7278cfb13c1b"
|
| 32 |
},
|
| 33 |
"REPORT.md": {
|
| 34 |
"bytes": 32272,
|
| 35 |
+
"sha256": "9205011a4c547b8cf537d5b3538d25f1b8a9545c9bb020694f6d94deb26683f7"
|
| 36 |
},
|
| 37 |
"SOURCE_README.md": {
|
| 38 |
+
"bytes": 20535,
|
| 39 |
+
"sha256": "64539b78efae390a42ca19a718b8e7354327826fff81dc6c1a9c7841b11132e0"
|
| 40 |
},
|
| 41 |
"app.py": {
|
| 42 |
"bytes": 10381,
|
|
|
|
| 108 |
},
|
| 109 |
"export_manifest.json": {
|
| 110 |
"bytes": 12430,
|
| 111 |
+
"sha256": "1f70d8ec79c9c2430c9d3a16942f51254202279c984048e02a781a33313f242b"
|
| 112 |
},
|
| 113 |
"model.onnx": {
|
| 114 |
"bytes": 625431,
|
|
|
|
| 283 |
"sha256": "89167b269b2103ad0bd94918f2ec4f7b605cf001d391e570683ceef39066b6c2"
|
| 284 |
},
|
| 285 |
"scripts/publish_hf.py": {
|
| 286 |
+
"bytes": 43530,
|
| 287 |
+
"sha256": "36311a2457bb0e93cbd6fd0ed444e57a2ec2c44ab9bd17ed06132e9640a9ddc3"
|
| 288 |
},
|
| 289 |
"scripts/replay_stream.py": {
|
| 290 |
"bytes": 3752,
|
|
|
|
| 527 |
"sha256": "12a9a0edda07265de293076d12073a707b10e6fb9ebbf2e7c70ba9a219289bdc"
|
| 528 |
},
|
| 529 |
"tests/test_publish_hf.py": {
|
| 530 |
+
"bytes": 25161,
|
| 531 |
+
"sha256": "5af81ad7995b6bfd3816b7d97ee1226027659dc5b8a7cb4fe4376fa850dcd14e"
|
| 532 |
},
|
| 533 |
"tests/test_release_guardrails.py": {
|
| 534 |
"bytes": 17399,
|
|
|
|
| 566 |
},
|
| 567 |
"MODEL_CARD.md": {
|
| 568 |
"bytes": 14805,
|
| 569 |
+
"sha256": "6add0853cdc0f09ff1abb594149c59e9d53f29ab4dba5de52bed7278cfb13c1b"
|
| 570 |
},
|
| 571 |
"NOTICE": {
|
| 572 |
"bytes": 563,
|
|
|
|
| 626 |
"metrics_file": "development_metrics.json",
|
| 627 |
"metrics_scope": "development",
|
| 628 |
"model_file": "model.onnx",
|
| 629 |
+
"source_and_artifact_inventory_sha256": "4c327046a4f44513d65a2a920a67566c646bf9c82e1f770a7f1ee6614006a47e"
|
| 630 |
}
|
scripts/publish_hf.py
CHANGED
|
@@ -306,6 +306,12 @@ def _failure_kind(exc: BaseException) -> str:
|
|
| 306 |
status = _http_status(exc)
|
| 307 |
if status in {401, 403}:
|
| 308 |
return f"authentication/authorization rejection (HTTP {status})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
if status is not None:
|
| 310 |
return f"HTTP {status}"
|
| 311 |
chain = _exception_chain(exc)
|
|
@@ -495,6 +501,7 @@ def _ensure_repo(
|
|
| 495 |
repo_id: str,
|
| 496 |
repo_type: str,
|
| 497 |
private: bool,
|
|
|
|
| 498 |
policy: RetryPolicy,
|
| 499 |
) -> Any:
|
| 500 |
info = _repo_info_or_none(api, repo_id=repo_id, repo_type=repo_type, policy=policy)
|
|
@@ -507,6 +514,8 @@ def _ensure_repo(
|
|
| 507 |
}
|
| 508 |
if repo_type == "space":
|
| 509 |
create_kwargs["space_sdk"] = "gradio"
|
|
|
|
|
|
|
| 510 |
_call_with_retry(
|
| 511 |
f"create {repo_type} repository",
|
| 512 |
lambda: api.create_repo(**create_kwargs),
|
|
@@ -604,12 +613,14 @@ def _publish_component(
|
|
| 604 |
release_kind: str,
|
| 605 |
inventory_digest: str,
|
| 606 |
policy: RetryPolicy,
|
|
|
|
| 607 |
) -> ComponentResult:
|
| 608 |
_ensure_repo(
|
| 609 |
api,
|
| 610 |
repo_id=repo_id,
|
| 611 |
repo_type=repo_type,
|
| 612 |
private=private,
|
|
|
|
| 613 |
policy=policy,
|
| 614 |
)
|
| 615 |
info, initial = _current_verification(
|
|
@@ -840,6 +851,10 @@ def _execute_publish(
|
|
| 840 |
"model": f"{args.username}/{args.model_name}",
|
| 841 |
"space": f"{args.username}/{args.space_name}",
|
| 842 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 843 |
"status": "starting",
|
| 844 |
"updated_at": _utc_now(),
|
| 845 |
}
|
|
@@ -914,6 +929,7 @@ def _execute_publish(
|
|
| 914 |
release_kind=release_kind,
|
| 915 |
inventory_digest=manifest["release_inventory_sha256"],
|
| 916 |
policy=policy,
|
|
|
|
| 917 |
)
|
| 918 |
receipt["space"] = space.as_dict()
|
| 919 |
receipt["status"] = "space_content_verified"
|
|
@@ -1018,6 +1034,14 @@ def parse_args() -> argparse.Namespace:
|
|
| 1018 |
parser.add_argument("--username", default="suvradeepp")
|
| 1019 |
parser.add_argument("--model-name", default="tiny-hinglish-turn-detector")
|
| 1020 |
parser.add_argument("--space-name", default="tiny-hinglish-turn-detector")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1021 |
parser.add_argument("--release-dir", default="release")
|
| 1022 |
parser.add_argument("--receipt", default="reports/hf_publish_receipt.json")
|
| 1023 |
parser.add_argument("--private", action="store_true")
|
|
|
|
| 306 |
status = _http_status(exc)
|
| 307 |
if status in {401, 403}:
|
| 308 |
return f"authentication/authorization rejection (HTTP {status})"
|
| 309 |
+
if status == 402:
|
| 310 |
+
return (
|
| 311 |
+
"Hugging Face plan/hardware entitlement required (HTTP 402); "
|
| 312 |
+
"verify free ZeroGPU eligibility and an available slot for "
|
| 313 |
+
"--space-hardware zero-a10g, otherwise upgrade the account plan"
|
| 314 |
+
)
|
| 315 |
if status is not None:
|
| 316 |
return f"HTTP {status}"
|
| 317 |
chain = _exception_chain(exc)
|
|
|
|
| 501 |
repo_id: str,
|
| 502 |
repo_type: str,
|
| 503 |
private: bool,
|
| 504 |
+
space_hardware: str | None,
|
| 505 |
policy: RetryPolicy,
|
| 506 |
) -> Any:
|
| 507 |
info = _repo_info_or_none(api, repo_id=repo_id, repo_type=repo_type, policy=policy)
|
|
|
|
| 514 |
}
|
| 515 |
if repo_type == "space":
|
| 516 |
create_kwargs["space_sdk"] = "gradio"
|
| 517 |
+
if space_hardware is not None:
|
| 518 |
+
create_kwargs["space_hardware"] = space_hardware
|
| 519 |
_call_with_retry(
|
| 520 |
f"create {repo_type} repository",
|
| 521 |
lambda: api.create_repo(**create_kwargs),
|
|
|
|
| 613 |
release_kind: str,
|
| 614 |
inventory_digest: str,
|
| 615 |
policy: RetryPolicy,
|
| 616 |
+
space_hardware: str | None = None,
|
| 617 |
) -> ComponentResult:
|
| 618 |
_ensure_repo(
|
| 619 |
api,
|
| 620 |
repo_id=repo_id,
|
| 621 |
repo_type=repo_type,
|
| 622 |
private=private,
|
| 623 |
+
space_hardware=space_hardware if repo_type == "space" else None,
|
| 624 |
policy=policy,
|
| 625 |
)
|
| 626 |
info, initial = _current_verification(
|
|
|
|
| 851 |
"model": f"{args.username}/{args.model_name}",
|
| 852 |
"space": f"{args.username}/{args.space_name}",
|
| 853 |
},
|
| 854 |
+
"space_creation": {
|
| 855 |
+
"sdk": "gradio",
|
| 856 |
+
"hardware_if_created": args.space_hardware,
|
| 857 |
+
},
|
| 858 |
"status": "starting",
|
| 859 |
"updated_at": _utc_now(),
|
| 860 |
}
|
|
|
|
| 929 |
release_kind=release_kind,
|
| 930 |
inventory_digest=manifest["release_inventory_sha256"],
|
| 931 |
policy=policy,
|
| 932 |
+
space_hardware=args.space_hardware,
|
| 933 |
)
|
| 934 |
receipt["space"] = space.as_dict()
|
| 935 |
receipt["status"] = "space_content_verified"
|
|
|
|
| 1034 |
parser.add_argument("--username", default="suvradeepp")
|
| 1035 |
parser.add_argument("--model-name", default="tiny-hinglish-turn-detector")
|
| 1036 |
parser.add_argument("--space-name", default="tiny-hinglish-turn-detector")
|
| 1037 |
+
parser.add_argument(
|
| 1038 |
+
"--space-hardware",
|
| 1039 |
+
default="zero-a10g",
|
| 1040 |
+
help=(
|
| 1041 |
+
"Hardware requested only when creating a new Space (default: zero-a10g, "
|
| 1042 |
+
"the free-account-compatible ZeroGPU tier)"
|
| 1043 |
+
),
|
| 1044 |
+
)
|
| 1045 |
parser.add_argument("--release-dir", default="release")
|
| 1046 |
parser.add_argument("--receipt", default="reports/hf_publish_receipt.json")
|
| 1047 |
parser.add_argument("--private", action="store_true")
|
tests/test_publish_hf.py
CHANGED
|
@@ -10,6 +10,7 @@ from io import StringIO
|
|
| 10 |
from pathlib import Path
|
| 11 |
from types import SimpleNamespace
|
| 12 |
from typing import Any
|
|
|
|
| 13 |
|
| 14 |
from scripts.publish_hf import (
|
| 15 |
PublishFailure,
|
|
@@ -24,6 +25,7 @@ from scripts.publish_hf import (
|
|
| 24 |
_publish_component,
|
| 25 |
_validate_inventory,
|
| 26 |
_wait_for_space,
|
|
|
|
| 27 |
)
|
| 28 |
|
| 29 |
|
|
@@ -37,6 +39,7 @@ class FakeHubApi:
|
|
| 37 |
def __init__(self) -> None:
|
| 38 |
self.repos: dict[tuple[str, str], dict[str, Any]] = {}
|
| 39 |
self.history: dict[tuple[str, str, str], dict[str, bytes]] = {}
|
|
|
|
| 40 |
self.upload_calls: list[dict[str, Any]] = []
|
| 41 |
self.runtime_stages = ["BUILDING", "RUNNING"]
|
| 42 |
self.upload_mode = "normal"
|
|
@@ -94,6 +97,7 @@ class FakeHubApi:
|
|
| 94 |
)
|
| 95 |
|
| 96 |
def create_repo(self, **kwargs: Any) -> None:
|
|
|
|
| 97 |
key = (kwargs["repo_type"], kwargs["repo_id"])
|
| 98 |
if key not in self.repos:
|
| 99 |
self.add_repo(
|
|
@@ -183,6 +187,7 @@ class PublishHfTest(unittest.TestCase):
|
|
| 183 |
*,
|
| 184 |
repo_type: str = "model",
|
| 185 |
replace_existing: bool = False,
|
|
|
|
| 186 |
):
|
| 187 |
return _publish_component(
|
| 188 |
api,
|
|
@@ -197,8 +202,63 @@ class PublishHfTest(unittest.TestCase):
|
|
| 197 |
release_kind="development preview",
|
| 198 |
inventory_digest="a" * 64,
|
| 199 |
policy=self.policy,
|
|
|
|
| 200 |
)
|
| 201 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
def test_unmanaged_existing_repo_requires_explicit_replacement(self) -> None:
|
| 203 |
api = FakeHubApi()
|
| 204 |
api.add_repo("suvradeepp/test-model", "model", {"README.md": b"unrelated"})
|
|
@@ -387,6 +447,7 @@ class PublishHfTest(unittest.TestCase):
|
|
| 387 |
username="suvradeepp",
|
| 388 |
model_name="test-model",
|
| 389 |
space_name="test-space",
|
|
|
|
| 390 |
receipt=str(receipt_path),
|
| 391 |
private=False,
|
| 392 |
replace_existing=False,
|
|
@@ -474,6 +535,7 @@ class PublishHfTest(unittest.TestCase):
|
|
| 474 |
username="suvradeepp",
|
| 475 |
model_name="test-model",
|
| 476 |
space_name="test-space",
|
|
|
|
| 477 |
receipt=str(receipt),
|
| 478 |
private=False,
|
| 479 |
replace_existing=False,
|
|
@@ -491,6 +553,10 @@ class PublishHfTest(unittest.TestCase):
|
|
| 491 |
policy=self.policy,
|
| 492 |
)
|
| 493 |
self.assertEqual(result["status"], "content_verified_runtime_running")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
persisted = receipt.read_text(encoding="utf-8")
|
| 495 |
self.assertNotIn("secret-token", persisted)
|
| 496 |
self.assertEqual(json.loads(persisted)["space"]["runtime_stage"], "RUNNING")
|
|
@@ -543,6 +609,7 @@ class PublishHfTest(unittest.TestCase):
|
|
| 543 |
username="suvradeepp",
|
| 544 |
model_name="test-model",
|
| 545 |
space_name="test-space",
|
|
|
|
| 546 |
receipt=str(receipt_path),
|
| 547 |
private=False,
|
| 548 |
replace_existing=False,
|
|
|
|
| 10 |
from pathlib import Path
|
| 11 |
from types import SimpleNamespace
|
| 12 |
from typing import Any
|
| 13 |
+
from unittest.mock import patch
|
| 14 |
|
| 15 |
from scripts.publish_hf import (
|
| 16 |
PublishFailure,
|
|
|
|
| 25 |
_publish_component,
|
| 26 |
_validate_inventory,
|
| 27 |
_wait_for_space,
|
| 28 |
+
parse_args,
|
| 29 |
)
|
| 30 |
|
| 31 |
|
|
|
|
| 39 |
def __init__(self) -> None:
|
| 40 |
self.repos: dict[tuple[str, str], dict[str, Any]] = {}
|
| 41 |
self.history: dict[tuple[str, str, str], dict[str, bytes]] = {}
|
| 42 |
+
self.create_calls: list[dict[str, Any]] = []
|
| 43 |
self.upload_calls: list[dict[str, Any]] = []
|
| 44 |
self.runtime_stages = ["BUILDING", "RUNNING"]
|
| 45 |
self.upload_mode = "normal"
|
|
|
|
| 97 |
)
|
| 98 |
|
| 99 |
def create_repo(self, **kwargs: Any) -> None:
|
| 100 |
+
self.create_calls.append(dict(kwargs))
|
| 101 |
key = (kwargs["repo_type"], kwargs["repo_id"])
|
| 102 |
if key not in self.repos:
|
| 103 |
self.add_repo(
|
|
|
|
| 187 |
*,
|
| 188 |
repo_type: str = "model",
|
| 189 |
replace_existing: bool = False,
|
| 190 |
+
space_hardware: str | None = None,
|
| 191 |
):
|
| 192 |
return _publish_component(
|
| 193 |
api,
|
|
|
|
| 202 |
release_kind="development preview",
|
| 203 |
inventory_digest="a" * 64,
|
| 204 |
policy=self.policy,
|
| 205 |
+
space_hardware=space_hardware,
|
| 206 |
)
|
| 207 |
|
| 208 |
+
def test_space_hardware_is_sent_only_when_creating_a_new_space(self) -> None:
|
| 209 |
+
api = FakeHubApi()
|
| 210 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 211 |
+
folder = Path(directory)
|
| 212 |
+
(folder / "payload.bin").write_bytes(b"payload")
|
| 213 |
+
self._component(api, folder)
|
| 214 |
+
self._component(
|
| 215 |
+
api,
|
| 216 |
+
folder,
|
| 217 |
+
repo_type="space",
|
| 218 |
+
space_hardware="zero-a10g",
|
| 219 |
+
)
|
| 220 |
+
self._component(
|
| 221 |
+
api,
|
| 222 |
+
folder,
|
| 223 |
+
repo_type="space",
|
| 224 |
+
space_hardware="cpu-basic",
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
model_create, space_create = api.create_calls
|
| 228 |
+
self.assertNotIn("space_hardware", model_create)
|
| 229 |
+
self.assertEqual(space_create["space_sdk"], "gradio")
|
| 230 |
+
self.assertEqual(space_create["space_hardware"], "zero-a10g")
|
| 231 |
+
|
| 232 |
+
def test_space_hardware_cli_defaults_to_zerogpu(self) -> None:
|
| 233 |
+
with patch("sys.argv", ["publish_hf.py"]):
|
| 234 |
+
self.assertEqual(parse_args().space_hardware, "zero-a10g")
|
| 235 |
+
|
| 236 |
+
def test_space_create_402_is_actionable_sanitized_and_not_retried(self) -> None:
|
| 237 |
+
secret = "hf_do_not_print_me"
|
| 238 |
+
|
| 239 |
+
class PlanGatedApi(FakeHubApi):
|
| 240 |
+
def create_repo(self, **kwargs: Any) -> None:
|
| 241 |
+
self.create_calls.append(dict(kwargs))
|
| 242 |
+
raise FakeHttpError(402, f"billing failure containing {secret}")
|
| 243 |
+
|
| 244 |
+
api = PlanGatedApi()
|
| 245 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 246 |
+
folder = Path(directory)
|
| 247 |
+
(folder / "payload.bin").write_bytes(b"payload")
|
| 248 |
+
with self.assertRaises(PublishFailure) as caught:
|
| 249 |
+
self._component(
|
| 250 |
+
api,
|
| 251 |
+
folder,
|
| 252 |
+
repo_type="space",
|
| 253 |
+
space_hardware="zero-a10g",
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
message = str(caught.exception)
|
| 257 |
+
self.assertEqual(len(api.create_calls), 1)
|
| 258 |
+
self.assertIn("HTTP 402", message)
|
| 259 |
+
self.assertIn("--space-hardware zero-a10g", message)
|
| 260 |
+
self.assertNotIn(secret, message)
|
| 261 |
+
|
| 262 |
def test_unmanaged_existing_repo_requires_explicit_replacement(self) -> None:
|
| 263 |
api = FakeHubApi()
|
| 264 |
api.add_repo("suvradeepp/test-model", "model", {"README.md": b"unrelated"})
|
|
|
|
| 447 |
username="suvradeepp",
|
| 448 |
model_name="test-model",
|
| 449 |
space_name="test-space",
|
| 450 |
+
space_hardware="zero-a10g",
|
| 451 |
receipt=str(receipt_path),
|
| 452 |
private=False,
|
| 453 |
replace_existing=False,
|
|
|
|
| 535 |
username="suvradeepp",
|
| 536 |
model_name="test-model",
|
| 537 |
space_name="test-space",
|
| 538 |
+
space_hardware="zero-a10g",
|
| 539 |
receipt=str(receipt),
|
| 540 |
private=False,
|
| 541 |
replace_existing=False,
|
|
|
|
| 553 |
policy=self.policy,
|
| 554 |
)
|
| 555 |
self.assertEqual(result["status"], "content_verified_runtime_running")
|
| 556 |
+
self.assertEqual(
|
| 557 |
+
result["space_creation"],
|
| 558 |
+
{"sdk": "gradio", "hardware_if_created": "zero-a10g"},
|
| 559 |
+
)
|
| 560 |
persisted = receipt.read_text(encoding="utf-8")
|
| 561 |
self.assertNotIn("secret-token", persisted)
|
| 562 |
self.assertEqual(json.loads(persisted)["space"]["runtime_stage"], "RUNNING")
|
|
|
|
| 609 |
username="suvradeepp",
|
| 610 |
model_name="test-model",
|
| 611 |
space_name="test-space",
|
| 612 |
+
space_hardware="zero-a10g",
|
| 613 |
receipt=str(receipt_path),
|
| 614 |
private=False,
|
| 615 |
replace_existing=False,
|