ap-client 0.5.1.dev4__tar.gz → 0.6.0.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/PKG-INFO +4 -3
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/api.py +3 -3
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/cli.py +83 -3
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/dataset_commands.py +53 -27
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/dataset_register.py +53 -6
- ap_client-0.6.0.dev0/ap_client/exporter.py +729 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/instance_commands.py +18 -3
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/irepo_sdk.py +133 -37
- ap_client-0.6.0.dev0/ap_client/managed_ak.py +177 -0
- ap_client-0.6.0.dev0/ap_client/manifest_view.py +105 -0
- ap_client-0.6.0.dev0/ap_client/repo_read_guard.py +45 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/pyproject.toml +4 -3
- ap_client-0.5.1.dev4/ap_client/exporter.py +0 -934
- ap_client-0.5.1.dev4/ap_client/managed_ak.py +0 -86
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/__init__.py +0 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/akpool_commands.py +0 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/config.py +0 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/fs_commands.py +0 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/profile_commands.py +0 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/split_publish.py +0 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/tbb.py +0 -0
- {ap_client-0.5.1.dev4 → ap_client-0.6.0.dev0}/ap_client/waiter.py +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0.dev0
|
|
4
4
|
Summary: Agent Platform API Client & CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: irepo-artifact==0.2.1
|
|
6
7
|
Requires-Dist: pyyaml>=6.0
|
|
7
8
|
Requires-Dist: requests>=2.28.0
|
|
8
9
|
Requires-Dist: rich>=13.0.0
|
|
9
10
|
Requires-Dist: typer>=0.9.0
|
|
10
11
|
Requires-Dist: websockets>=13.0
|
|
11
12
|
Provides-Extra: all
|
|
12
|
-
Requires-Dist: instance-repo[oss]
|
|
13
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.3.2; extra == 'all'
|
|
13
14
|
Provides-Extra: dataset
|
|
14
|
-
Requires-Dist: instance-repo[oss]
|
|
15
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.3.2; extra == 'dataset'
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
|
|
17
18
|
A lightweight Python SDK and command line interface for Agent Platform. It provides helpers for configuring API access and managing templates, datasets, jobs, and groups.
|
|
@@ -539,11 +539,11 @@ class APIClient:
|
|
|
539
539
|
|
|
540
540
|
def _post(self, path: str, data: Any, timeout: TimeoutType = None) -> Any:
|
|
541
541
|
"""Send a POST request."""
|
|
542
|
-
from .managed_ak import
|
|
542
|
+
from .managed_ak import parameter_ak_usage, require_parameter_ak_ack
|
|
543
543
|
|
|
544
|
-
references =
|
|
544
|
+
references, selectors = parameter_ak_usage(data) if path == "/jobs" else (set(), set())
|
|
545
545
|
result = self._request("POST", path, json_body=data, timeout=timeout)
|
|
546
|
-
require_parameter_ak_ack(result, references)
|
|
546
|
+
require_parameter_ak_ack(result, references, selectors)
|
|
547
547
|
return result
|
|
548
548
|
|
|
549
549
|
def _delete(self, path: str, *, params: Optional[dict] = None) -> Any:
|
|
@@ -25,7 +25,7 @@ from ap_client.api import (
|
|
|
25
25
|
)
|
|
26
26
|
from ap_client.config import ENV_VAR_SPECS, ConfigurationError, _parse_bool, normalize_output_format
|
|
27
27
|
from ap_client.dataset_commands import register as _register_dataset_commands
|
|
28
|
-
from ap_client.exporter import export_group, export_job
|
|
28
|
+
from ap_client.exporter import export_group, export_job, export_jobs
|
|
29
29
|
from ap_client.fs_commands import register as _register_fs_commands
|
|
30
30
|
from ap_client.instance_commands import register as _register_instance_commands
|
|
31
31
|
from ap_client.managed_ak import (
|
|
@@ -3564,8 +3564,8 @@ def job_create(
|
|
|
3564
3564
|
if dry_run:
|
|
3565
3565
|
_emit_dry_run_warnings(dry_run_warnings)
|
|
3566
3566
|
body["dry_run"] = True
|
|
3567
|
-
result = client._post("/jobs", body, timeout=timeout)
|
|
3568
3567
|
try:
|
|
3568
|
+
result = client._post("/jobs", body, timeout=timeout)
|
|
3569
3569
|
require_managed_ak_ack(result, ak_id)
|
|
3570
3570
|
except ManagedAKProtocolError as exc:
|
|
3571
3571
|
_emit_error(str(exc))
|
|
@@ -4876,10 +4876,47 @@ def job_artifacts(
|
|
|
4876
4876
|
_print_formatted(results, output_format)
|
|
4877
4877
|
|
|
4878
4878
|
|
|
4879
|
+
def _validate_export_timeout(value: float) -> float:
|
|
4880
|
+
import math
|
|
4881
|
+
|
|
4882
|
+
if not math.isfinite(value) or value <= 0:
|
|
4883
|
+
raise typer.BadParameter("Timeout must be a finite number greater than zero")
|
|
4884
|
+
return value
|
|
4885
|
+
|
|
4886
|
+
|
|
4887
|
+
def _export_channel_reporter(emit):
|
|
4888
|
+
"""Report completed Jobs, including callbacks from Group worker threads."""
|
|
4889
|
+
from threading import Lock
|
|
4890
|
+
|
|
4891
|
+
from rich.text import Text
|
|
4892
|
+
|
|
4893
|
+
counts = {"oss": 0, "proxy": 0}
|
|
4894
|
+
lock = Lock()
|
|
4895
|
+
|
|
4896
|
+
def report(job_id, status, channel):
|
|
4897
|
+
with lock:
|
|
4898
|
+
suffix = ""
|
|
4899
|
+
if status == "downloaded" and channel in counts:
|
|
4900
|
+
counts[channel] += 1
|
|
4901
|
+
suffix = f" channel={channel}"
|
|
4902
|
+
emit(Text(f"{job_id} {status}{suffix}"))
|
|
4903
|
+
|
|
4904
|
+
return report, counts
|
|
4905
|
+
|
|
4906
|
+
|
|
4879
4907
|
@job_app.command("export")
|
|
4880
4908
|
def job_export(
|
|
4881
|
-
job_id: str = typer.Argument(..., help="Job ID"),
|
|
4909
|
+
job_id: str = typer.Argument(..., help="Job ID or comma-separated Job IDs"),
|
|
4882
4910
|
output: Optional[Path] = typer.Option(None, "--output", "-o", help="Export directory"),
|
|
4911
|
+
workers: int = typer.Option(
|
|
4912
|
+
1, "--workers", "-w", min=1, help="Number of concurrent export workers"
|
|
4913
|
+
),
|
|
4914
|
+
timeout: float = typer.Option(
|
|
4915
|
+
300,
|
|
4916
|
+
"--timeout",
|
|
4917
|
+
callback=_validate_export_timeout,
|
|
4918
|
+
help="SDK request timeout in seconds, not the total export duration",
|
|
4919
|
+
),
|
|
4883
4920
|
logs: bool = typer.Option(False, "--logs", help="Include container logs"),
|
|
4884
4921
|
events: bool = typer.Option(False, "--events", help="Include events"),
|
|
4885
4922
|
no_extract_artifacts: bool = typer.Option(
|
|
@@ -4889,17 +4926,50 @@ def job_export(
|
|
|
4889
4926
|
),
|
|
4890
4927
|
):
|
|
4891
4928
|
"""Export job artifacts to a local directory. Use --logs/--events to include logs and events."""
|
|
4929
|
+
ids = list(dict.fromkeys(value.strip() for value in job_id.split(",")))
|
|
4930
|
+
if not all(ids):
|
|
4931
|
+
raise typer.BadParameter("Job IDs must not be empty", param_hint="job_id")
|
|
4892
4932
|
client = get_client()
|
|
4933
|
+
if len(ids) > 1:
|
|
4934
|
+
report, channels = _export_channel_reporter(print)
|
|
4935
|
+
dest, summary = export_jobs(
|
|
4936
|
+
client,
|
|
4937
|
+
ids,
|
|
4938
|
+
output,
|
|
4939
|
+
workers=workers,
|
|
4940
|
+
result_callback=report,
|
|
4941
|
+
timeout=timeout,
|
|
4942
|
+
include_logs=logs,
|
|
4943
|
+
include_events=events,
|
|
4944
|
+
extract_artifacts=not no_extract_artifacts,
|
|
4945
|
+
)
|
|
4946
|
+
print(
|
|
4947
|
+
f"Jobs exported: downloaded={summary.downloaded} "
|
|
4948
|
+
f"skipped={summary.skipped} failed={len(summary.failures)}"
|
|
4949
|
+
)
|
|
4950
|
+
print(f"channels: oss={channels['oss']} proxy={channels['proxy']}")
|
|
4951
|
+
print(f" path: {dest}")
|
|
4952
|
+
for failed_id, error in summary.failures.items():
|
|
4953
|
+
print(f"[red]Failed job {failed_id}:[/] {error}")
|
|
4954
|
+
if summary.failures:
|
|
4955
|
+
raise typer.Exit(code=1)
|
|
4956
|
+
return
|
|
4957
|
+
job_id = ids[0]
|
|
4958
|
+
result = {}
|
|
4893
4959
|
dest = export_job(
|
|
4894
4960
|
client,
|
|
4895
4961
|
job_id,
|
|
4896
4962
|
output,
|
|
4963
|
+
result_callback=lambda jid, status, channel: result.update(status=status, channel=channel),
|
|
4964
|
+
timeout=timeout,
|
|
4897
4965
|
include_logs=logs,
|
|
4898
4966
|
include_events=events,
|
|
4899
4967
|
extract_artifacts=not no_extract_artifacts,
|
|
4900
4968
|
)
|
|
4901
4969
|
print(f"[green]Job exported:[/] {job_id}")
|
|
4902
4970
|
print(f" path: {dest}")
|
|
4971
|
+
if result.get("channel") in {"oss", "proxy"} and result.get("status") == "downloaded":
|
|
4972
|
+
print(f" channel: {result['channel']}")
|
|
4903
4973
|
|
|
4904
4974
|
|
|
4905
4975
|
@job_app.command("wait")
|
|
@@ -6374,6 +6444,12 @@ def group_export(
|
|
|
6374
6444
|
workers: int = typer.Option(
|
|
6375
6445
|
4, "--workers", "-w", min=1, help="Number of concurrent export workers"
|
|
6376
6446
|
),
|
|
6447
|
+
timeout: float = typer.Option(
|
|
6448
|
+
300,
|
|
6449
|
+
"--timeout",
|
|
6450
|
+
callback=_validate_export_timeout,
|
|
6451
|
+
help="SDK request timeout in seconds, not the total export duration",
|
|
6452
|
+
),
|
|
6377
6453
|
logs: bool = typer.Option(False, "--logs", help="Include container logs"),
|
|
6378
6454
|
events: bool = typer.Option(False, "--events", help="Include events"),
|
|
6379
6455
|
no_extract_artifacts: bool = typer.Option(
|
|
@@ -6434,12 +6510,15 @@ def group_export(
|
|
|
6434
6510
|
current_stage=current_stage,
|
|
6435
6511
|
)
|
|
6436
6512
|
|
|
6513
|
+
report, channels = _export_channel_reporter(progress.console.print)
|
|
6437
6514
|
dest, summary = export_group(
|
|
6438
6515
|
client,
|
|
6439
6516
|
group_id,
|
|
6440
6517
|
output,
|
|
6441
6518
|
progress_callback=_on_progress,
|
|
6442
6519
|
workers=workers,
|
|
6520
|
+
result_callback=report,
|
|
6521
|
+
timeout=timeout,
|
|
6443
6522
|
include_logs=logs,
|
|
6444
6523
|
include_events=events,
|
|
6445
6524
|
extract_artifacts=not no_extract_artifacts,
|
|
@@ -6452,6 +6531,7 @@ def group_export(
|
|
|
6452
6531
|
f" [green]{summary.downloaded}[/] jobs downloaded, "
|
|
6453
6532
|
f"[yellow]{summary.skipped}[/] jobs skipped (no artifact)"
|
|
6454
6533
|
)
|
|
6534
|
+
print(f"channels: oss={channels['oss']} proxy={channels['proxy']}")
|
|
6455
6535
|
if summary.skipped_job_names:
|
|
6456
6536
|
print(f" Skipped: {', '.join(summary.skipped_job_names)}")
|
|
6457
6537
|
|
|
@@ -15,6 +15,9 @@ from typing import TYPE_CHECKING, Any, List, Optional
|
|
|
15
15
|
|
|
16
16
|
import typer
|
|
17
17
|
|
|
18
|
+
from .repo_read_guard import STORAGE_SIDES as _STORAGE_SIDES
|
|
19
|
+
from .repo_read_guard import oss_fallback_kwargs
|
|
20
|
+
|
|
18
21
|
if TYPE_CHECKING: # pragma: no cover - 仅用于类型标注,运行时不 import
|
|
19
22
|
from datetime import datetime
|
|
20
23
|
|
|
@@ -316,13 +319,8 @@ def _env_kwargs(environment: str) -> dict:
|
|
|
316
319
|
return {"environment": environment} if environment and environment != "auto" else {}
|
|
317
320
|
|
|
318
321
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"dev": "pre",
|
|
322
|
-
"internal-dev": "pre",
|
|
323
|
-
"external-dev": "pre",
|
|
324
|
-
"prod": "online",
|
|
325
|
-
}
|
|
322
|
+
def _read_env_kwargs(repo: Any, client: Any, method: str, environment: str) -> dict:
|
|
323
|
+
return {**_env_kwargs(environment), **oss_fallback_kwargs(repo, client, method, environment)}
|
|
326
324
|
|
|
327
325
|
|
|
328
326
|
def _check_storage_side(repo: Any, environment: str) -> None:
|
|
@@ -419,7 +417,9 @@ def _model_dict(record: Any) -> dict:
|
|
|
419
417
|
key: value
|
|
420
418
|
for key, value in payload.items()
|
|
421
419
|
if value is not None
|
|
422
|
-
and (
|
|
420
|
+
and (
|
|
421
|
+
key == "registered" or not (value is False or value == "" or value == [] or value == {})
|
|
422
|
+
)
|
|
423
423
|
}
|
|
424
424
|
|
|
425
425
|
|
|
@@ -609,7 +609,7 @@ def _fetch_version(
|
|
|
609
609
|
detail = repo.versions.get(
|
|
610
610
|
dataset,
|
|
611
611
|
version,
|
|
612
|
-
**
|
|
612
|
+
**_read_env_kwargs(repo, repo.versions, "get", env),
|
|
613
613
|
**({"metadata_model": metadata_model} if metadata_model else {}),
|
|
614
614
|
)
|
|
615
615
|
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
@@ -646,19 +646,30 @@ def _unversioned_splits(
|
|
|
646
646
|
env = _environment(environment)
|
|
647
647
|
repo = _dataset_repo()
|
|
648
648
|
try:
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
649
|
+
records: list[dict] = []
|
|
650
|
+
page = 1
|
|
651
|
+
while True:
|
|
652
|
+
result = repo.splits.list(
|
|
653
|
+
dataset,
|
|
654
|
+
version_scope="unversioned",
|
|
655
|
+
page_size=_SPLIT_PAGE_SIZE,
|
|
656
|
+
**({"page": page} if page > 1 else {}),
|
|
657
|
+
**_read_env_kwargs(repo, repo.splits, "list", env),
|
|
658
|
+
)
|
|
659
|
+
items = getattr(result, "items", None) or []
|
|
660
|
+
for item in items:
|
|
661
|
+
record = _model_dict(item)
|
|
662
|
+
if not _version_value(record.get("version")):
|
|
663
|
+
records.append(record)
|
|
664
|
+
if not items or getattr(result, "has_more", False) is not True:
|
|
665
|
+
break
|
|
666
|
+
page += 1
|
|
656
667
|
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
657
668
|
raise _fail(
|
|
658
669
|
f"dataset {dataset} (no version space): {stage} failed: {_reason(exc)}",
|
|
659
670
|
f"Check the dataset name and permission: ap dataset get {dataset}",
|
|
660
671
|
) from exc
|
|
661
|
-
return
|
|
672
|
+
return records
|
|
662
673
|
|
|
663
674
|
|
|
664
675
|
def _unversioned_split(
|
|
@@ -670,13 +681,14 @@ def _unversioned_split(
|
|
|
670
681
|
env = _environment(environment)
|
|
671
682
|
repo = _dataset_repo()
|
|
672
683
|
try:
|
|
673
|
-
record = repo.splits.get(dataset, split, **
|
|
684
|
+
record = repo.splits.get(dataset, split, **_read_env_kwargs(repo, repo.splits, "get", env))
|
|
674
685
|
except Exception as exc: # noqa: BLE001
|
|
675
686
|
raise _fail(
|
|
676
687
|
f"dataset {dataset} (no version space) split {split}: query failed: {_reason(exc)}",
|
|
677
688
|
f"Check the dataset name and permission: ap dataset get {dataset}",
|
|
678
689
|
) from exc
|
|
679
|
-
|
|
690
|
+
detail = _model_dict(record) if record is not None else None
|
|
691
|
+
return detail if detail is not None and not _version_value(detail.get("version")) else None
|
|
680
692
|
|
|
681
693
|
|
|
682
694
|
def _split_records(detail: dict) -> list[dict]:
|
|
@@ -1120,7 +1132,11 @@ def _print_dataset_environments(repo: "Repo", dataset: str, detail: dict) -> Non
|
|
|
1120
1132
|
rows: list[dict] = []
|
|
1121
1133
|
for env in envs:
|
|
1122
1134
|
try:
|
|
1123
|
-
env_detail = _model_dict(
|
|
1135
|
+
env_detail = _model_dict(
|
|
1136
|
+
repo.datasets.get_detail(
|
|
1137
|
+
dataset, **_read_env_kwargs(repo, repo.datasets, "get_detail", env)
|
|
1138
|
+
)
|
|
1139
|
+
)
|
|
1124
1140
|
except Exception: # noqa: BLE001 - 摘要尽力而为
|
|
1125
1141
|
rows.append({"environment": env, "versions": "?", "splits": "?", "latest_version": "?"})
|
|
1126
1142
|
continue
|
|
@@ -1155,7 +1171,9 @@ def dataset_get(
|
|
|
1155
1171
|
env = _environment(environment)
|
|
1156
1172
|
repo = _dataset_repo()
|
|
1157
1173
|
try:
|
|
1158
|
-
detail = repo.datasets.get_detail(
|
|
1174
|
+
detail = repo.datasets.get_detail(
|
|
1175
|
+
dataset, **_read_env_kwargs(repo, repo.datasets, "get_detail", env)
|
|
1176
|
+
)
|
|
1159
1177
|
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
1160
1178
|
if _business_code(exc) not in (92001, 94021):
|
|
1161
1179
|
raise _fail(
|
|
@@ -1164,13 +1182,13 @@ def dataset_get(
|
|
|
1164
1182
|
) from exc
|
|
1165
1183
|
detail = None
|
|
1166
1184
|
if detail is None:
|
|
1167
|
-
l1, _, l2 = dataset.partition("/")
|
|
1168
1185
|
rendered = {
|
|
1169
1186
|
"dataset_name": dataset,
|
|
1170
|
-
"dataset_l1": l1,
|
|
1171
|
-
"dataset_l2": l2,
|
|
1172
1187
|
"registered": False,
|
|
1173
1188
|
}
|
|
1189
|
+
parts = dataset.split("/")
|
|
1190
|
+
if len(parts) == 2 and all(parts):
|
|
1191
|
+
rendered.update(dataset_l1=parts[0], dataset_l2=parts[1])
|
|
1174
1192
|
else:
|
|
1175
1193
|
rendered = _registered_dict(detail)
|
|
1176
1194
|
_emit_detail(rendered, output_format, order=_DATASET_DETAIL_ORDER, always=("status",))
|
|
@@ -1294,7 +1312,11 @@ def dataset_claim(
|
|
|
1294
1312
|
env = _environment(environment)
|
|
1295
1313
|
repo = _dataset_repo()
|
|
1296
1314
|
try:
|
|
1297
|
-
detail = _model_dict(
|
|
1315
|
+
detail = _model_dict(
|
|
1316
|
+
repo.datasets.get_detail(
|
|
1317
|
+
dataset, **_read_env_kwargs(repo, repo.datasets, "get_detail", env)
|
|
1318
|
+
)
|
|
1319
|
+
)
|
|
1298
1320
|
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
1299
1321
|
raise _fail(
|
|
1300
1322
|
f"dataset {dataset}: pre-claim query failed: {_reason(exc)}",
|
|
@@ -1372,7 +1394,11 @@ def dataset_claim(
|
|
|
1372
1394
|
owner = str(detail.get("owner") or "").strip()
|
|
1373
1395
|
if not owner:
|
|
1374
1396
|
try:
|
|
1375
|
-
fresh = _model_dict(
|
|
1397
|
+
fresh = _model_dict(
|
|
1398
|
+
repo.datasets.get_detail(
|
|
1399
|
+
dataset, **_read_env_kwargs(repo, repo.datasets, "get_detail", env)
|
|
1400
|
+
)
|
|
1401
|
+
)
|
|
1376
1402
|
owner = str(fresh.get("owner") or "").strip()
|
|
1377
1403
|
except Exception: # noqa: BLE001 - 取不到 owner 不影响幂等退出
|
|
1378
1404
|
owner = ""
|
|
@@ -1658,7 +1684,7 @@ def dataset_version_list(
|
|
|
1658
1684
|
page=page,
|
|
1659
1685
|
page_size=page_size,
|
|
1660
1686
|
**({"metadata_model": model} if model else {}),
|
|
1661
|
-
**
|
|
1687
|
+
**_read_env_kwargs(repo, repo.versions, "list_paged", env),
|
|
1662
1688
|
)
|
|
1663
1689
|
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
1664
1690
|
raise _fail(
|
|
@@ -42,7 +42,9 @@ def _confirm(dataset: str, *, yes: bool) -> None:
|
|
|
42
42
|
"Add --yes (-y) to confirm the registration",
|
|
43
43
|
exit_code=2,
|
|
44
44
|
)
|
|
45
|
-
if not typer.confirm(
|
|
45
|
+
if not typer.confirm(
|
|
46
|
+
f"Register the previewed data for dataset {dataset}?", default=False, err=True
|
|
47
|
+
):
|
|
46
48
|
raise datasets._fail(
|
|
47
49
|
"Registration canceled; nothing was written remotely",
|
|
48
50
|
"Rerun the command and confirm, or add --yes (-y)",
|
|
@@ -52,6 +54,8 @@ def _confirm(dataset: str, *, yes: bool) -> None:
|
|
|
52
54
|
def _claim_without_workspace(
|
|
53
55
|
repo: Any, dataset_id: str, current_user: str, benchmark_id: str
|
|
54
56
|
) -> dict:
|
|
57
|
+
# API allows omitted workspace (ClaimAllowsOmittedWorkspace). SDK 1.3.2's
|
|
58
|
+
# claim() still rejects it locally, so keep the authenticated transport shim.
|
|
55
59
|
body = {"owner": current_user}
|
|
56
60
|
if benchmark_id:
|
|
57
61
|
body["benchmark_id"] = benchmark_id
|
|
@@ -128,6 +132,22 @@ def _ensure_current_user_owns(
|
|
|
128
132
|
f"Check the name and benchmark, then inspect: ap dataset get {dataset}",
|
|
129
133
|
) from create_exc
|
|
130
134
|
created_owner = str(created.get("owner") or "").strip()
|
|
135
|
+
if not created_owner:
|
|
136
|
+
try:
|
|
137
|
+
refreshed = datasets._model_dict(
|
|
138
|
+
repo.datasets.get_detail(dataset, **datasets._env_kwargs(environment))
|
|
139
|
+
)
|
|
140
|
+
created_owner = str(refreshed.get("owner") or "").strip()
|
|
141
|
+
except Exception as exc: # noqa: BLE001 - creation may already have succeeded
|
|
142
|
+
raise datasets._fail(
|
|
143
|
+
f"Dataset {dataset} was created, but ownership could not be verified: {_reason(exc)}",
|
|
144
|
+
f"Inspect ownership before registering again: ap dataset get {dataset}",
|
|
145
|
+
) from exc
|
|
146
|
+
if not created_owner:
|
|
147
|
+
raise datasets._fail(
|
|
148
|
+
f"Dataset {dataset} was created, but the server returned no owner",
|
|
149
|
+
f"Verify ownership before registering again: ap dataset get {dataset}",
|
|
150
|
+
)
|
|
131
151
|
if created_owner != current_user:
|
|
132
152
|
shown_owner = created_owner or "<unknown>"
|
|
133
153
|
raise datasets._fail(
|
|
@@ -173,12 +193,16 @@ def dataset_register(
|
|
|
173
193
|
version: Optional[str] = typer.Option(None, "--version", help=datasets._VERSION_HELP),
|
|
174
194
|
splits: Optional[List[str]] = typer.Option(None, "--split", help="Split to scan; repeatable"),
|
|
175
195
|
unversioned: bool = typer.Option(
|
|
176
|
-
False,
|
|
196
|
+
False,
|
|
197
|
+
"--unversioned",
|
|
198
|
+
help="Scan the version-less space; mutually exclusive with --version",
|
|
177
199
|
),
|
|
178
200
|
environment: Optional[str] = typer.Option(
|
|
179
201
|
None, "--environment", help=datasets._ENVIRONMENT_HELP
|
|
180
202
|
),
|
|
181
|
-
dry_run: bool = typer.Option(
|
|
203
|
+
dry_run: bool = typer.Option(
|
|
204
|
+
False, "--dry-run", help="Preview without submitting registration"
|
|
205
|
+
),
|
|
182
206
|
yes: bool = typer.Option(False, "--yes", "-y", help="Skip the registration confirmation"),
|
|
183
207
|
wait: bool = typer.Option(False, "--wait", help="Wait for the ingest task to finish"),
|
|
184
208
|
timeout: float = typer.Option(1800.0, "--timeout", help="Local wait timeout in seconds"),
|
|
@@ -194,6 +218,13 @@ def dataset_register(
|
|
|
194
218
|
from .irepo_sdk import _reason
|
|
195
219
|
|
|
196
220
|
fmt = datasets._fmt(output_format)
|
|
221
|
+
dataset = dataset.strip()
|
|
222
|
+
if not dataset:
|
|
223
|
+
raise datasets._fail(
|
|
224
|
+
"Failed to register dataset: name is empty",
|
|
225
|
+
"Pass a non-empty dataset name, e.g. ap dataset register alibaba/a",
|
|
226
|
+
exit_code=2,
|
|
227
|
+
)
|
|
197
228
|
selected_version = str(version or "").strip() or None
|
|
198
229
|
selected_splits = [str(value).strip() for value in (splits or []) if str(value).strip()]
|
|
199
230
|
if selected_version and unversioned:
|
|
@@ -258,9 +289,25 @@ def dataset_register(
|
|
|
258
289
|
preview_payload = _payload("preview", dataset, preview)
|
|
259
290
|
if dry_run:
|
|
260
291
|
datasets._emit_detail(preview_payload, fmt)
|
|
292
|
+
else:
|
|
293
|
+
_emit_preview_stderr(preview_payload, fmt)
|
|
294
|
+
preview_status = str(preview.get("status") or "").strip().lower()
|
|
295
|
+
if preview_status in _TERMINAL_FAILURE | _TERMINAL_CANCELED:
|
|
296
|
+
raise datasets._fail(
|
|
297
|
+
f"Registration preview {preview_status}: "
|
|
298
|
+
f"{preview.get('error_message') or preview.get('error_msg') or 'no details returned'}",
|
|
299
|
+
"Fix the preview error before submitting registration",
|
|
300
|
+
)
|
|
301
|
+
if dry_run:
|
|
261
302
|
return
|
|
262
|
-
|
|
263
|
-
|
|
303
|
+
counts = preview.get("counts")
|
|
304
|
+
if preview.get("instance_count") == 0 or (
|
|
305
|
+
isinstance(counts, dict) and counts.get("instances") == 0
|
|
306
|
+
):
|
|
307
|
+
raise datasets._fail(
|
|
308
|
+
"Registration preview found no instances",
|
|
309
|
+
"Check the dataset scope and storage layout before submitting registration",
|
|
310
|
+
)
|
|
264
311
|
_confirm(dataset, yes=yes)
|
|
265
312
|
if owner == "@me":
|
|
266
313
|
_ensure_current_user_owns(repo, dataset, env, benchmark)
|
|
@@ -305,7 +352,7 @@ def dataset_register(
|
|
|
305
352
|
if outcome == "failed":
|
|
306
353
|
raise datasets._fail(
|
|
307
354
|
f"Registration task {ingest_id or '<unknown>'} failed: "
|
|
308
|
-
f"{result.get('error_message') or ''}",
|
|
355
|
+
f"{result.get('error_message') or result.get('error_msg') or 'no details returned'}",
|
|
309
356
|
"Fix the reported data or metadata issue, then run the same registration command again",
|
|
310
357
|
)
|
|
311
358
|
if outcome == "canceled":
|