ap-client 0.5.0.dev0__tar.gz → 0.5.1.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.0.dev0 → ap_client-0.5.1.dev0}/PKG-INFO +3 -3
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/akpool_commands.py +1 -3
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/api.py +23 -53
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/cli.py +30 -11
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/dataset_commands.py +40 -14
- ap_client-0.5.1.dev0/ap_client/dataset_register.py +320 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/instance_commands.py +4 -1
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/pyproject.toml +3 -3
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/__init__.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/config.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/exporter.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/fs_commands.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/irepo_sdk.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/managed_ak.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/profile_commands.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/split_publish.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/tbb.py +0 -0
- {ap_client-0.5.0.dev0 → ap_client-0.5.1.dev0}/ap_client/waiter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1.dev0
|
|
4
4
|
Summary: Agent Platform API Client & CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: pyyaml>=6.0
|
|
@@ -9,9 +9,9 @@ Requires-Dist: rich>=13.0.0
|
|
|
9
9
|
Requires-Dist: typer>=0.9.0
|
|
10
10
|
Requires-Dist: websockets>=13.0
|
|
11
11
|
Provides-Extra: all
|
|
12
|
-
Requires-Dist: instance-repo[oss]
|
|
12
|
+
Requires-Dist: instance-repo[oss]==1.3.2.dev0; extra == 'all'
|
|
13
13
|
Provides-Extra: dataset
|
|
14
|
-
Requires-Dist: instance-repo[oss]
|
|
14
|
+
Requires-Dist: instance-repo[oss]==1.3.2.dev0; extra == 'dataset'
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
|
|
17
17
|
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.
|
|
@@ -63,9 +63,7 @@ def _fmt(output_format: Optional[str]) -> str:
|
|
|
63
63
|
def _check_time_range(value: str) -> str:
|
|
64
64
|
normalized = str(value or "").strip().lower()
|
|
65
65
|
if not _TIME_RANGE_RE.fullmatch(normalized):
|
|
66
|
-
raise typer.BadParameter(
|
|
67
|
-
f"--time-range must be a duration like 30s/5m/1h, got {value!r}"
|
|
68
|
-
)
|
|
66
|
+
raise typer.BadParameter(f"--time-range must be a duration like 30s/5m/1h, got {value!r}")
|
|
69
67
|
return normalized
|
|
70
68
|
|
|
71
69
|
|
|
@@ -123,14 +123,6 @@ _UNSET: Any = object()
|
|
|
123
123
|
# when aggregation runs later.
|
|
124
124
|
EXTRA_AGG_OPS = ("avg", "sum")
|
|
125
125
|
|
|
126
|
-
# Others 占位:apiserver 词表不返回,CLI 展示与 get_meta_model("Others") 兜底用。
|
|
127
|
-
_OTHERS_MODEL = {
|
|
128
|
-
"name": "Others",
|
|
129
|
-
"series_name": "",
|
|
130
|
-
"source_id": "",
|
|
131
|
-
"series_id": "",
|
|
132
|
-
}
|
|
133
|
-
|
|
134
126
|
|
|
135
127
|
def _merge_meta_tags(
|
|
136
128
|
tags: Optional[list],
|
|
@@ -976,18 +968,19 @@ class APIClient:
|
|
|
976
968
|
# `dataset versions/instances`、`job create --dataset`)。
|
|
977
969
|
|
|
978
970
|
def list_meta_models(self) -> dict:
|
|
979
|
-
"""
|
|
980
|
-
|
|
971
|
+
"""从 meta-model 标签目录获取可用模型,保留 list 的 items 响应形状。"""
|
|
972
|
+
items = self.get_meta_tags("meta-model").get("items") or []
|
|
973
|
+
return {
|
|
974
|
+
"items": [
|
|
975
|
+
{**item, "name": item["value"]}
|
|
976
|
+
for item in items
|
|
977
|
+
if item.get("value") and item.get("is_enabled", True)
|
|
978
|
+
]
|
|
979
|
+
}
|
|
981
980
|
|
|
982
981
|
def get_meta_model(self, name: str) -> dict:
|
|
983
|
-
"""
|
|
984
|
-
|
|
985
|
-
apiserver 没有单模型详情接口,客户端从 list 结果过滤;找不到 raise
|
|
986
|
-
APIError 404,与原 cluster AP /meta/models/{name} 行为一致。
|
|
987
|
-
"""
|
|
988
|
-
if name == "Others":
|
|
989
|
-
return dict(_OTHERS_MODEL)
|
|
990
|
-
result = self._get_central("/meta-models")
|
|
982
|
+
"""从启用的 meta-model 标签值中按 value 或 source_id 查找。"""
|
|
983
|
+
result = self.list_meta_models()
|
|
991
984
|
for item in result.get("items") or []:
|
|
992
985
|
if item.get("name") == name or item.get("source_id") == name:
|
|
993
986
|
return item
|
|
@@ -1053,43 +1046,13 @@ class APIClient:
|
|
|
1053
1046
|
return result
|
|
1054
1047
|
|
|
1055
1048
|
def list_meta_job_types(self) -> dict:
|
|
1056
|
-
"""
|
|
1057
|
-
|
|
1058
|
-
仅取目录中 ``is_enabled`` 的值(缺失按启用解析)+ 末尾 ``Others``
|
|
1059
|
-
兜底行;``default`` 为 ``Eval-Eval``(在目录中时,否则空串)。
|
|
1060
|
-
目录请求失败或为空 → 回退本地常量(Eval-Eval + Others,fail-open),
|
|
1061
|
-
并在 stderr 打一行告警,词表降级可见但不阻塞命令。
|
|
1062
|
-
"""
|
|
1063
|
-
error: Exception | None = None
|
|
1064
|
-
try:
|
|
1065
|
-
items = self.get_meta_tags("meta-job-type").get("items") or []
|
|
1066
|
-
except Exception as exc: # fail-open:目录不可用不阻塞词表命令
|
|
1067
|
-
error = exc
|
|
1068
|
-
items = []
|
|
1069
|
-
if items:
|
|
1070
|
-
options = []
|
|
1071
|
-
for item in items:
|
|
1072
|
-
value = str(item.get("value") or "").strip()
|
|
1073
|
-
if value and item.get("is_enabled", True):
|
|
1074
|
-
options.append({"value": value})
|
|
1075
|
-
if options:
|
|
1076
|
-
default = "Eval-Eval" if any(o["value"] == "Eval-Eval" for o in options) else ""
|
|
1077
|
-
return {
|
|
1078
|
-
"meta_job_type": {
|
|
1079
|
-
"default": default,
|
|
1080
|
-
"options": [*options, {"value": "Others"}],
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
_write_stderr(
|
|
1084
|
-
[
|
|
1085
|
-
"warning: meta-job-type catalog unavailable or empty, "
|
|
1086
|
-
f"falling back to local constants (reason={error or 'empty catalog'})"
|
|
1087
|
-
]
|
|
1088
|
-
)
|
|
1049
|
+
"""从 meta-job-type 标签目录获取启用的任务类型,不补本地展示值。"""
|
|
1050
|
+
items = self.get_meta_tags("meta-job-type").get("items") or []
|
|
1089
1051
|
return {
|
|
1090
1052
|
"meta_job_type": {
|
|
1091
|
-
"
|
|
1092
|
-
|
|
1053
|
+
"options": [
|
|
1054
|
+
item for item in items if item.get("value") and item.get("is_enabled", True)
|
|
1055
|
+
],
|
|
1093
1056
|
}
|
|
1094
1057
|
}
|
|
1095
1058
|
|
|
@@ -1283,6 +1246,7 @@ class APIClient:
|
|
|
1283
1246
|
benchmark: Optional[str] = None,
|
|
1284
1247
|
experiment: Optional[str] = None,
|
|
1285
1248
|
ak_id: Optional[str] = None,
|
|
1249
|
+
ak_mode: str = "auto",
|
|
1286
1250
|
instance_range: Optional[str] = None,
|
|
1287
1251
|
profile: Optional[str] = None,
|
|
1288
1252
|
) -> dict:
|
|
@@ -1334,6 +1298,7 @@ class APIClient:
|
|
|
1334
1298
|
benchmark=benchmark,
|
|
1335
1299
|
experiment=experiment,
|
|
1336
1300
|
ak_id=ak_id,
|
|
1301
|
+
ak_mode=ak_mode,
|
|
1337
1302
|
)
|
|
1338
1303
|
|
|
1339
1304
|
result = self._post("/jobs", body, timeout=timeout)
|
|
@@ -1395,6 +1360,7 @@ class APIClient:
|
|
|
1395
1360
|
benchmark: Optional[str] = None,
|
|
1396
1361
|
experiment: Optional[str] = None,
|
|
1397
1362
|
ak_id: Optional[str] = None,
|
|
1363
|
+
ak_mode: str = "auto",
|
|
1398
1364
|
instance_range: Optional[str] = None,
|
|
1399
1365
|
profile: Optional[str] = None,
|
|
1400
1366
|
) -> dict:
|
|
@@ -1410,11 +1376,15 @@ class APIClient:
|
|
|
1410
1376
|
if not template and not profile_id and profile is None:
|
|
1411
1377
|
raise ValueError("Either template, profile or profile_id is required")
|
|
1412
1378
|
validate_ak_selection(ak_id, params, params_list, overrides)
|
|
1379
|
+
if ak_mode not in {"auto", "manual", "synced"}:
|
|
1380
|
+
raise ValueError("ak_mode must be auto, manual or synced")
|
|
1413
1381
|
body: dict = {}
|
|
1414
1382
|
if template is not None:
|
|
1415
1383
|
body["template"] = template
|
|
1416
1384
|
if ak_id is not None:
|
|
1417
1385
|
body["ak_id"] = ak_id
|
|
1386
|
+
if ak_mode != "auto":
|
|
1387
|
+
body["ak_mode"] = ak_mode
|
|
1418
1388
|
|
|
1419
1389
|
if params is not None:
|
|
1420
1390
|
body["params"] = params
|
|
@@ -15,7 +15,6 @@ import typer
|
|
|
15
15
|
from ap_client import __version__, get_client, get_config, tbb
|
|
16
16
|
from ap_client.akpool_commands import register as _register_akpool_commands
|
|
17
17
|
from ap_client.api import (
|
|
18
|
-
_OTHERS_MODEL,
|
|
19
18
|
APIError,
|
|
20
19
|
PaginationProtocolError,
|
|
21
20
|
_is_sensitive_key,
|
|
@@ -267,12 +266,15 @@ def _build_job_create_retry_command(
|
|
|
267
266
|
resource_profile_id: Optional[str] = None,
|
|
268
267
|
timeout: Optional[float] = None,
|
|
269
268
|
ak_id: Optional[str] = None,
|
|
269
|
+
ak_mode: str = "auto",
|
|
270
270
|
) -> str:
|
|
271
271
|
args = ["ap", "job", "create"]
|
|
272
272
|
if template is not None:
|
|
273
273
|
args.append(template)
|
|
274
274
|
if ak_id is not None:
|
|
275
275
|
args.extend(["--ak-id", ak_id])
|
|
276
|
+
if ak_mode != "auto":
|
|
277
|
+
args.extend(["--ak-mode", ak_mode])
|
|
276
278
|
if profile is not None:
|
|
277
279
|
args.extend(["--profile", profile])
|
|
278
280
|
if resource_profile_id:
|
|
@@ -1240,6 +1242,7 @@ def _idempotency_item_key(
|
|
|
1240
1242
|
resource_profile_id: Optional[str] = None,
|
|
1241
1243
|
max_failure_retries: int = 0,
|
|
1242
1244
|
ak_id: Optional[str] = None,
|
|
1245
|
+
ak_mode: str = "auto",
|
|
1243
1246
|
) -> str:
|
|
1244
1247
|
# Client-supplied item keys are authoritative; server hashing is only a
|
|
1245
1248
|
# fallback for non-CLI callers, so this payload intentionally need not
|
|
@@ -1259,6 +1262,8 @@ def _idempotency_item_key(
|
|
|
1259
1262
|
}
|
|
1260
1263
|
if ak_id is not None:
|
|
1261
1264
|
payload_data["ak_id"] = ak_id
|
|
1265
|
+
if ak_mode != "auto":
|
|
1266
|
+
payload_data["ak_mode"] = ak_mode
|
|
1262
1267
|
if resource_profile_id is not None:
|
|
1263
1268
|
payload_data["resource_profile_id"] = resource_profile_id
|
|
1264
1269
|
if max_failure_retries:
|
|
@@ -2683,23 +2688,22 @@ def meta_model_list(
|
|
|
2683
2688
|
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
2684
2689
|
),
|
|
2685
2690
|
):
|
|
2686
|
-
"""List meta
|
|
2691
|
+
"""List enabled meta-model tag values."""
|
|
2687
2692
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
2688
2693
|
result = get_client().list_meta_models()
|
|
2689
2694
|
items = result.get("items") or []
|
|
2690
|
-
options =
|
|
2695
|
+
options = items
|
|
2691
2696
|
if output_format in ("plain", "table"):
|
|
2692
2697
|
rows = [
|
|
2693
2698
|
{
|
|
2694
2699
|
"name": m.get("name") or "-",
|
|
2695
|
-
"
|
|
2700
|
+
"desc": m.get("desc_l1") or "-",
|
|
2696
2701
|
}
|
|
2697
2702
|
for m in options
|
|
2698
2703
|
]
|
|
2699
|
-
_print_plain_table(rows, [("name", "name"), ("
|
|
2704
|
+
_print_plain_table(rows, [("name", "name"), ("desc", "desc")])
|
|
2700
2705
|
else:
|
|
2701
|
-
#
|
|
2702
|
-
# default 已随 meta-models default 逻辑移除而下线)
|
|
2706
|
+
# JSON/YAML 保持 {meta_model:{options}};每项保留目录字段及 name 别名。
|
|
2703
2707
|
_print_formatted({"meta_model": {"options": options}}, output_format)
|
|
2704
2708
|
|
|
2705
2709
|
|
|
@@ -2790,21 +2794,20 @@ def meta_job_type_list(
|
|
|
2790
2794
|
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
2791
2795
|
),
|
|
2792
2796
|
):
|
|
2793
|
-
"""List meta
|
|
2797
|
+
"""List enabled meta-job-type tag values."""
|
|
2794
2798
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
2795
2799
|
result = get_client().list_meta_job_types()
|
|
2796
2800
|
info = result.get("meta_job_type") or {}
|
|
2797
2801
|
options = info.get("options") or []
|
|
2798
|
-
default = info.get("default") or ""
|
|
2799
2802
|
if output_format in ("plain", "table"):
|
|
2800
2803
|
rows = [
|
|
2801
2804
|
{
|
|
2802
2805
|
"value": o.get("value") or "-",
|
|
2803
|
-
"
|
|
2806
|
+
"desc": o.get("desc_l1") or "-",
|
|
2804
2807
|
}
|
|
2805
2808
|
for o in options
|
|
2806
2809
|
]
|
|
2807
|
-
_print_plain_table(rows, [("value", "value"), ("
|
|
2810
|
+
_print_plain_table(rows, [("value", "value"), ("desc", "desc")])
|
|
2808
2811
|
else:
|
|
2809
2812
|
_print_formatted(result, output_format)
|
|
2810
2813
|
|
|
@@ -2990,6 +2993,9 @@ def job_create(
|
|
|
2990
2993
|
ak_id: Optional[str] = typer.Option(
|
|
2991
2994
|
None, "--ak-id", help="AP-managed AK ID; requires server support"
|
|
2992
2995
|
),
|
|
2996
|
+
ak_mode: str = typer.Option(
|
|
2997
|
+
"auto", "--ak-mode", help="AK credential source: auto, manual or synced"
|
|
2998
|
+
),
|
|
2993
2999
|
agenthub_ref: Optional[str] = typer.Option(
|
|
2994
3000
|
None, "--agenthub-ref", help="Agent-Hub branch/commit; overrides AP_AGENTHUB_REF"
|
|
2995
3001
|
),
|
|
@@ -3332,6 +3338,8 @@ def job_create(
|
|
|
3332
3338
|
output_format = _normalize_output_format(output_format)
|
|
3333
3339
|
if not template and not profile:
|
|
3334
3340
|
raise typer.BadParameter("TEMPLATE is required unless --profile is provided")
|
|
3341
|
+
if ak_mode not in {"auto", "manual", "synced"}:
|
|
3342
|
+
raise typer.BadParameter("Choose auto, manual or synced", param_hint="--ak-mode")
|
|
3335
3343
|
try:
|
|
3336
3344
|
validate_ak_id(ak_id)
|
|
3337
3345
|
resource_profile_id = _normalize_resource_profile_id(resource_profile_id)
|
|
@@ -3528,6 +3536,7 @@ def job_create(
|
|
|
3528
3536
|
profile=profile,
|
|
3529
3537
|
resource_profile_id=resource_profile_id,
|
|
3530
3538
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
3539
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
3531
3540
|
instances=instances_list,
|
|
3532
3541
|
instance_range=instance_range,
|
|
3533
3542
|
dataset=dataset,
|
|
@@ -3601,6 +3610,7 @@ def job_create(
|
|
|
3601
3610
|
profile=profile,
|
|
3602
3611
|
resource_profile_id=resource_profile_id,
|
|
3603
3612
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
3613
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
3604
3614
|
timeout=timeout,
|
|
3605
3615
|
)
|
|
3606
3616
|
if idempotency_enabled and submission_idempotency_key is not None
|
|
@@ -3645,6 +3655,7 @@ def job_create(
|
|
|
3645
3655
|
profile=profile,
|
|
3646
3656
|
resource_profile_id=resource_profile_id,
|
|
3647
3657
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
3658
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
3648
3659
|
instances=instances_list,
|
|
3649
3660
|
instance_range=instance_range,
|
|
3650
3661
|
dataset=dataset,
|
|
@@ -3860,6 +3871,7 @@ def job_create(
|
|
|
3860
3871
|
idempotency_key=submission_idempotency_key,
|
|
3861
3872
|
resource_profile_id=resource_profile_id,
|
|
3862
3873
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
3874
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
3863
3875
|
timeout=timeout,
|
|
3864
3876
|
)
|
|
3865
3877
|
if not dry_run
|
|
@@ -3956,6 +3968,7 @@ def job_create(
|
|
|
3956
3968
|
account_pool=account_pool,
|
|
3957
3969
|
resource_profile_id=resource_profile_id,
|
|
3958
3970
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
3971
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
3959
3972
|
max_failure_retries=max_failure_retries,
|
|
3960
3973
|
)
|
|
3961
3974
|
batch.append(item)
|
|
@@ -3989,6 +4002,7 @@ def job_create(
|
|
|
3989
4002
|
account_pool=account_pool,
|
|
3990
4003
|
resource_profile_id=resource_profile_id,
|
|
3991
4004
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
4005
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
3992
4006
|
checkpoint_id=checkpoint_id,
|
|
3993
4007
|
checkpoint_path=checkpoint_path,
|
|
3994
4008
|
checkpoint=checkpoint_dict,
|
|
@@ -4032,6 +4046,7 @@ def job_create(
|
|
|
4032
4046
|
account_pool=account_pool,
|
|
4033
4047
|
resource_profile_id=resource_profile_id,
|
|
4034
4048
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
4049
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
4035
4050
|
checkpoint_id=checkpoint_id,
|
|
4036
4051
|
checkpoint_path=checkpoint_path,
|
|
4037
4052
|
checkpoint=checkpoint_dict,
|
|
@@ -4089,6 +4104,7 @@ def job_create(
|
|
|
4089
4104
|
account_pool=account_pool,
|
|
4090
4105
|
resource_profile_id=resource_profile_id,
|
|
4091
4106
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
4107
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
4092
4108
|
checkpoint_id=checkpoint_id,
|
|
4093
4109
|
checkpoint_path=checkpoint_path,
|
|
4094
4110
|
checkpoint=checkpoint_dict,
|
|
@@ -4195,6 +4211,7 @@ def job_create(
|
|
|
4195
4211
|
account_pool=account_pool,
|
|
4196
4212
|
resource_profile_id=resource_profile_id,
|
|
4197
4213
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
4214
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
4198
4215
|
checkpoint_id=checkpoint_id,
|
|
4199
4216
|
checkpoint_path=checkpoint_path,
|
|
4200
4217
|
checkpoint=checkpoint_dict,
|
|
@@ -4232,6 +4249,7 @@ def job_create(
|
|
|
4232
4249
|
account_pool=account_pool,
|
|
4233
4250
|
resource_profile_id=resource_profile_id,
|
|
4234
4251
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
4252
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
4235
4253
|
checkpoint_id=checkpoint_id,
|
|
4236
4254
|
checkpoint_path=checkpoint_path,
|
|
4237
4255
|
checkpoint=checkpoint_dict,
|
|
@@ -4279,6 +4297,7 @@ def job_create(
|
|
|
4279
4297
|
account_pool=account_pool,
|
|
4280
4298
|
resource_profile_id=resource_profile_id,
|
|
4281
4299
|
**({"ak_id": ak_id} if ak_id is not None else {}),
|
|
4300
|
+
**({"ak_mode": ak_mode} if ak_mode != "auto" else {}),
|
|
4282
4301
|
checkpoint_id=checkpoint_id,
|
|
4283
4302
|
checkpoint_path=checkpoint_path,
|
|
4284
4303
|
checkpoint=checkpoint_dict,
|
|
@@ -50,18 +50,15 @@ def register(dataset_app: typer.Typer) -> None:
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def _register_dataset_commands(dataset_app: typer.Typer) -> None:
|
|
53
|
-
"""挂载 dataset
|
|
53
|
+
"""挂载 dataset 级命令。"""
|
|
54
|
+
from .dataset_register import dataset_register
|
|
54
55
|
|
|
55
|
-
实现方式:把命令写成本模块的模块级函数,再在这里用
|
|
56
|
-
``dataset_app.command("<name>")(<func>)`` 挂上去。``list`` 需要**替换**
|
|
57
|
-
``cli.py`` 里现存的 ossdata 版本(设计 §4.1),挂载时用同名 ``"list"`` 覆盖,
|
|
58
|
-
并同步删掉 ``cli.py`` 中的旧实现。
|
|
59
|
-
"""
|
|
60
56
|
dataset_app.command("list")(dataset_list)
|
|
61
57
|
dataset_app.command("get")(dataset_get)
|
|
62
58
|
dataset_app.command("create")(dataset_create)
|
|
63
59
|
dataset_app.command("update")(dataset_update)
|
|
64
60
|
dataset_app.command("claim")(dataset_claim)
|
|
61
|
+
dataset_app.command("register")(dataset_register)
|
|
65
62
|
|
|
66
63
|
|
|
67
64
|
# ---------------------------------------------------------------------------
|
|
@@ -86,6 +83,7 @@ _VERSION_COLUMNS: tuple[tuple[str, str], ...] = (
|
|
|
86
83
|
("splits", "SPLITS"),
|
|
87
84
|
("instances", "INSTANCES"),
|
|
88
85
|
("updated_at", "UPDATED_AT"),
|
|
86
|
+
("registered", "REGISTERED"),
|
|
89
87
|
)
|
|
90
88
|
|
|
91
89
|
#: 上游 §8.2 的默认列(设计 §5)。
|
|
@@ -94,6 +92,7 @@ _SPLIT_COLUMNS: tuple[tuple[str, str], ...] = (
|
|
|
94
92
|
("run_type", "RUN_TYPE"),
|
|
95
93
|
("instances", "INSTANCES"),
|
|
96
94
|
("updated_at", "UPDATED_AT"),
|
|
95
|
+
("registered", "REGISTERED"),
|
|
97
96
|
)
|
|
98
97
|
|
|
99
98
|
_ACCESS_COLUMNS: tuple[tuple[str, str], ...] = (
|
|
@@ -398,6 +397,7 @@ def _model_dict(record: Any) -> dict:
|
|
|
398
397
|
``{}`` / 缺省都不会出现在响应里),而 SDK 模型会把它们补成默认值;留着这些默认值会让
|
|
399
398
|
JSON 凭空多出字段(迁移前是原样透出服务端响应,契约必须一致)。
|
|
400
399
|
|
|
400
|
+
``registered`` 是显式注册状态,必须保留 ``false``。
|
|
401
401
|
只清洗**顶层**:嵌套结构(如 version 详情里的 ``splits[].is_default``)按模型原样输出,
|
|
402
402
|
服务端在那里确实是显式返回 ``false``。
|
|
403
403
|
"""
|
|
@@ -412,13 +412,24 @@ def _model_dict(record: Any) -> dict:
|
|
|
412
412
|
return {}
|
|
413
413
|
if not isinstance(payload, dict):
|
|
414
414
|
return {}
|
|
415
|
+
registered = getattr(record, "registered", None)
|
|
416
|
+
if isinstance(registered, bool):
|
|
417
|
+
payload = {**payload, "registered": registered}
|
|
415
418
|
return {
|
|
416
419
|
key: value
|
|
417
420
|
for key, value in payload.items()
|
|
418
|
-
if
|
|
421
|
+
if value is not None
|
|
422
|
+
and (key == "registered" or not (value is False or value == "" or value == [] or value == {}))
|
|
419
423
|
}
|
|
420
424
|
|
|
421
425
|
|
|
426
|
+
def _registered_dict(record: Any) -> dict:
|
|
427
|
+
"""Preserve SDK registration state; older metadata-only SDK reads imply true."""
|
|
428
|
+
payload = _model_dict(record)
|
|
429
|
+
payload.setdefault("registered", True)
|
|
430
|
+
return payload
|
|
431
|
+
|
|
432
|
+
|
|
422
433
|
def _api_biz_code(exc: BaseException) -> Optional[int]:
|
|
423
434
|
"""从 ``APIError`` 里取 apiserver 业务码(设计 §5.1),不 import 数值异常类。
|
|
424
435
|
|
|
@@ -814,6 +825,7 @@ def _version_row(record: dict) -> dict:
|
|
|
814
825
|
"splits": splits,
|
|
815
826
|
"instances": record.get("instance_count", ""),
|
|
816
827
|
"updated_at": record.get("updated_at") or "",
|
|
828
|
+
"registered": record.get("registered", True),
|
|
817
829
|
}
|
|
818
830
|
|
|
819
831
|
|
|
@@ -833,6 +845,7 @@ def _split_row(record: dict, detail: dict) -> dict:
|
|
|
833
845
|
"run_type": record.get("run_type") or detail.get("run_type") or "",
|
|
834
846
|
"instances": "" if instances is None else instances,
|
|
835
847
|
"updated_at": record.get("updated_at") or detail.get("updated_at") or "",
|
|
848
|
+
"registered": record.get("registered", detail.get("registered", True)),
|
|
836
849
|
}
|
|
837
850
|
|
|
838
851
|
|
|
@@ -1144,11 +1157,22 @@ def dataset_get(
|
|
|
1144
1157
|
try:
|
|
1145
1158
|
detail = repo.datasets.get_detail(dataset, **_env_kwargs(env))
|
|
1146
1159
|
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1160
|
+
if _business_code(exc) not in (92001, 94021):
|
|
1161
|
+
raise _fail(
|
|
1162
|
+
f"Failed to query dataset {dataset}: {_reason(exc)}",
|
|
1163
|
+
f"Check the dataset name: ap dataset list --query {dataset}",
|
|
1164
|
+
) from exc
|
|
1165
|
+
detail = None
|
|
1166
|
+
if detail is None:
|
|
1167
|
+
l1, _, l2 = dataset.partition("/")
|
|
1168
|
+
rendered = {
|
|
1169
|
+
"dataset_name": dataset,
|
|
1170
|
+
"dataset_l1": l1,
|
|
1171
|
+
"dataset_l2": l2,
|
|
1172
|
+
"registered": False,
|
|
1173
|
+
}
|
|
1174
|
+
else:
|
|
1175
|
+
rendered = _registered_dict(detail)
|
|
1152
1176
|
_emit_detail(rendered, output_format, order=_DATASET_DETAIL_ORDER, always=("status",))
|
|
1153
1177
|
if output_format not in ("json", "yaml"):
|
|
1154
1178
|
_print_dataset_environments(repo, dataset, rendered)
|
|
@@ -1642,7 +1666,7 @@ def dataset_version_list(
|
|
|
1642
1666
|
f"Check the dataset name: ap dataset list --query {dataset}",
|
|
1643
1667
|
) from exc
|
|
1644
1668
|
|
|
1645
|
-
records = [
|
|
1669
|
+
records = [_registered_dict(record) for record in (getattr(result, "items", None) or [])]
|
|
1646
1670
|
rows = [_version_row(record) for record in records]
|
|
1647
1671
|
_emit(
|
|
1648
1672
|
{"dataset": dataset, "versions": records, "pagination": _pagination_envelope(result)},
|
|
@@ -1663,7 +1687,7 @@ def dataset_version_get(
|
|
|
1663
1687
|
output_format = _fmt(output_format)
|
|
1664
1688
|
resolved = _version_value(version)
|
|
1665
1689
|
detail = _fetch_version(dataset, resolved, stage="query", environment=environment)
|
|
1666
|
-
_emit_detail(detail, output_format, order=_VERSION_DETAIL_ORDER)
|
|
1690
|
+
_emit_detail(_registered_dict(detail), output_format, order=_VERSION_DETAIL_ORDER)
|
|
1667
1691
|
|
|
1668
1692
|
|
|
1669
1693
|
@dataset_version_app.command("create")
|
|
@@ -1810,6 +1834,7 @@ def dataset_split_get(
|
|
|
1810
1834
|
payload = dict(record)
|
|
1811
1835
|
payload.setdefault("dataset", dataset)
|
|
1812
1836
|
payload.setdefault("version", resolved_version)
|
|
1837
|
+
payload.setdefault("registered", detail.get("registered", True))
|
|
1813
1838
|
_emit_detail(payload, output_format)
|
|
1814
1839
|
return
|
|
1815
1840
|
known = ", ".join(_split_names(detail)) or "<none>"
|
|
@@ -1819,6 +1844,7 @@ def dataset_split_get(
|
|
|
1819
1844
|
payload = dict(record)
|
|
1820
1845
|
payload.setdefault("dataset", dataset)
|
|
1821
1846
|
payload.setdefault("version", "")
|
|
1847
|
+
payload.setdefault("registered", True)
|
|
1822
1848
|
_emit_detail(payload, output_format)
|
|
1823
1849
|
return
|
|
1824
1850
|
known = (
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""Dataset metadata registration command backed by Instance Repo ingest."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any, List, Optional
|
|
9
|
+
from urllib.parse import quote
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from . import dataset_commands as datasets
|
|
14
|
+
|
|
15
|
+
_TERMINAL_SUCCESS = {"completed"}
|
|
16
|
+
_TERMINAL_FAILURE = {"failed"}
|
|
17
|
+
_TERMINAL_CANCELED = {"canceled", "cancelled"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _payload(outcome: str, dataset: str, result: Any) -> dict:
|
|
21
|
+
return {**datasets._model_dict(result), "outcome": outcome, "dataset": dataset}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _emit_preview_stderr(payload: dict, output_format: str) -> None:
|
|
25
|
+
typer.echo("Preview:", err=True)
|
|
26
|
+
if output_format == "yaml":
|
|
27
|
+
try:
|
|
28
|
+
import yaml
|
|
29
|
+
except ImportError as exc:
|
|
30
|
+
raise typer.BadParameter("--format yaml requires PyYAML to be installed") from exc
|
|
31
|
+
typer.echo(yaml.safe_dump(payload, allow_unicode=True, sort_keys=False).rstrip(), err=True)
|
|
32
|
+
return
|
|
33
|
+
typer.echo(json.dumps(payload, ensure_ascii=False, indent=2), err=True)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _confirm(dataset: str, *, yes: bool) -> None:
|
|
37
|
+
if yes:
|
|
38
|
+
return
|
|
39
|
+
if not sys.stdin.isatty():
|
|
40
|
+
raise datasets._fail(
|
|
41
|
+
f"Registering dataset {dataset} requires confirmation after preview",
|
|
42
|
+
"Add --yes (-y) to confirm the registration",
|
|
43
|
+
exit_code=2,
|
|
44
|
+
)
|
|
45
|
+
if not typer.confirm(f"Register the previewed data for dataset {dataset}?", default=False, err=True):
|
|
46
|
+
raise datasets._fail(
|
|
47
|
+
"Registration canceled; nothing was written remotely",
|
|
48
|
+
"Rerun the command and confirm, or add --yes (-y)",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _claim_without_workspace(
|
|
53
|
+
repo: Any, dataset_id: str, current_user: str, benchmark_id: str
|
|
54
|
+
) -> dict:
|
|
55
|
+
body = {"owner": current_user}
|
|
56
|
+
if benchmark_id:
|
|
57
|
+
body["benchmark_id"] = benchmark_id
|
|
58
|
+
return repo.transport.post(
|
|
59
|
+
f"/apis/v1/datasets/series/{quote(dataset_id, safe='')}/claim",
|
|
60
|
+
body,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _create_owned_dataset(repo: Any, dataset: str, benchmark_id: str) -> dict:
|
|
65
|
+
parts = dataset.split("/")
|
|
66
|
+
if len(parts) != 2 or not all(parts):
|
|
67
|
+
raise datasets._fail(
|
|
68
|
+
f"Failed to create dataset {dataset}: the name must be two segments <team>/<name>",
|
|
69
|
+
"Use a dataset name such as your-team/my-bench",
|
|
70
|
+
exit_code=2,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
from instance_repo.models import DatasetSeries
|
|
74
|
+
|
|
75
|
+
fields: dict[str, Any] = {
|
|
76
|
+
"dataset_name": dataset,
|
|
77
|
+
"dataset_l1": parts[0],
|
|
78
|
+
"dataset_l2": parts[1],
|
|
79
|
+
"visibility": "private",
|
|
80
|
+
}
|
|
81
|
+
if benchmark_id:
|
|
82
|
+
fields["benchmark_id"] = benchmark_id
|
|
83
|
+
record = repo.datasets.create(DatasetSeries(**fields), require_benchmark=False)
|
|
84
|
+
return datasets._model_dict(record)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _ensure_current_user_owns(
|
|
88
|
+
repo: Any, dataset: str, environment: str, benchmark: Optional[str]
|
|
89
|
+
) -> None:
|
|
90
|
+
from . import cli
|
|
91
|
+
from .irepo_sdk import _reason
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
current_user = cli.get_client()._resolve_self_user_id()
|
|
95
|
+
except Exception as exc: # noqa: BLE001 - keep the CLI error contract
|
|
96
|
+
raise datasets._fail(
|
|
97
|
+
f"Failed to resolve the current user: {_reason(exc)}",
|
|
98
|
+
"Check AP_API_KEY and the current cluster, then retry",
|
|
99
|
+
) from exc
|
|
100
|
+
if not current_user:
|
|
101
|
+
raise datasets._fail(
|
|
102
|
+
"Failed to resolve the current user for --owner @me",
|
|
103
|
+
"Check AP_API_KEY and the current cluster, then retry",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
missing = False
|
|
107
|
+
try:
|
|
108
|
+
raw_detail = repo.datasets.get_detail(dataset, **datasets._env_kwargs(environment))
|
|
109
|
+
detail = datasets._model_dict(raw_detail)
|
|
110
|
+
missing = raw_detail is None or detail.get("registered") is False
|
|
111
|
+
except Exception as exc: # noqa: BLE001 - dataset absence permits create
|
|
112
|
+
if datasets._business_code(exc) not in (92001, 94021):
|
|
113
|
+
raise datasets._fail(
|
|
114
|
+
f"Failed to inspect ownership for dataset {dataset}: {_reason(exc)}",
|
|
115
|
+
f"Check access with: ap dataset get {dataset}",
|
|
116
|
+
) from exc
|
|
117
|
+
missing = True
|
|
118
|
+
detail = {}
|
|
119
|
+
if missing:
|
|
120
|
+
benchmark_id = datasets._resolve_benchmark_id(repo, benchmark) if benchmark else ""
|
|
121
|
+
try:
|
|
122
|
+
created = _create_owned_dataset(repo, dataset, benchmark_id)
|
|
123
|
+
except typer.Exit:
|
|
124
|
+
raise
|
|
125
|
+
except Exception as create_exc: # noqa: BLE001 - server remains authoritative
|
|
126
|
+
raise datasets._fail(
|
|
127
|
+
f"Failed to create dataset {dataset}: {_reason(create_exc)}",
|
|
128
|
+
f"Check the name and benchmark, then inspect: ap dataset get {dataset}",
|
|
129
|
+
) from create_exc
|
|
130
|
+
created_owner = str(created.get("owner") or "").strip()
|
|
131
|
+
if created_owner != current_user:
|
|
132
|
+
shown_owner = created_owner or "<unknown>"
|
|
133
|
+
raise datasets._fail(
|
|
134
|
+
f"Dataset {dataset} was created or claimed concurrently by {shown_owner}",
|
|
135
|
+
"Do not retry with --owner @me; ask the current dataset admin for access",
|
|
136
|
+
)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
owner = str(detail.get("owner") or "").strip()
|
|
140
|
+
if owner == current_user:
|
|
141
|
+
return
|
|
142
|
+
if owner:
|
|
143
|
+
raise datasets._fail(
|
|
144
|
+
f"Dataset {dataset} is owned by {owner}; --owner @me cannot take it over",
|
|
145
|
+
"Ask the existing dataset admin to run the registration or grant access",
|
|
146
|
+
)
|
|
147
|
+
if not detail.get("claimable", False):
|
|
148
|
+
raise datasets._fail(
|
|
149
|
+
f"Dataset {dataset} cannot be claimed because it already has an admin",
|
|
150
|
+
"Ask an existing dataset admin to run the registration or grant access",
|
|
151
|
+
)
|
|
152
|
+
dataset_id = str(detail.get("dataset_id") or "").strip()
|
|
153
|
+
if not dataset_id:
|
|
154
|
+
raise datasets._fail(
|
|
155
|
+
f"Failed to claim dataset {dataset}: the server returned no dataset_id",
|
|
156
|
+
f"Inspect the dataset with: ap dataset get {dataset}",
|
|
157
|
+
)
|
|
158
|
+
benchmark_id = datasets._resolve_benchmark_id(repo, benchmark) if benchmark else ""
|
|
159
|
+
try:
|
|
160
|
+
_claim_without_workspace(repo, dataset_id, current_user, benchmark_id)
|
|
161
|
+
except Exception as exc: # noqa: BLE001 - preserve SDK transport error mapping
|
|
162
|
+
hint = datasets._DATASET_CODE_HINTS.get(datasets._business_code(exc)) or (
|
|
163
|
+
"Ask an existing dataset admin for access, or verify that the dataset is still claimable"
|
|
164
|
+
)
|
|
165
|
+
raise datasets._fail(
|
|
166
|
+
f"Failed to claim dataset {dataset}: {_reason(exc)}",
|
|
167
|
+
hint,
|
|
168
|
+
) from exc
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def dataset_register(
|
|
172
|
+
dataset: str = typer.Argument(..., help="Dataset name"),
|
|
173
|
+
version: Optional[str] = typer.Option(None, "--version", help=datasets._VERSION_HELP),
|
|
174
|
+
splits: Optional[List[str]] = typer.Option(None, "--split", help="Split to scan; repeatable"),
|
|
175
|
+
unversioned: bool = typer.Option(
|
|
176
|
+
False, "--unversioned", help="Scan the version-less space; mutually exclusive with --version"
|
|
177
|
+
),
|
|
178
|
+
environment: Optional[str] = typer.Option(
|
|
179
|
+
None, "--environment", help=datasets._ENVIRONMENT_HELP
|
|
180
|
+
),
|
|
181
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Preview without submitting registration"),
|
|
182
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip the registration confirmation"),
|
|
183
|
+
wait: bool = typer.Option(False, "--wait", help="Wait for the ingest task to finish"),
|
|
184
|
+
timeout: float = typer.Option(1800.0, "--timeout", help="Local wait timeout in seconds"),
|
|
185
|
+
output_format: Optional[str] = typer.Option(None, "--format", help=datasets._FORMAT_HELP),
|
|
186
|
+
owner: Optional[str] = typer.Option(
|
|
187
|
+
None, "--owner", help="Ensure ownership before ingest; the only supported value is @me"
|
|
188
|
+
),
|
|
189
|
+
benchmark: Optional[str] = typer.Option(
|
|
190
|
+
None, "--benchmark", help="Benchmark for create/claim when using --owner @me"
|
|
191
|
+
),
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Preview and register existing dataset data through an ingest task."""
|
|
194
|
+
from .irepo_sdk import _reason
|
|
195
|
+
|
|
196
|
+
fmt = datasets._fmt(output_format)
|
|
197
|
+
selected_version = str(version or "").strip() or None
|
|
198
|
+
selected_splits = [str(value).strip() for value in (splits or []) if str(value).strip()]
|
|
199
|
+
if selected_version and unversioned:
|
|
200
|
+
raise datasets._fail(
|
|
201
|
+
"--version and --unversioned are mutually exclusive",
|
|
202
|
+
"Choose a version or the version-less space",
|
|
203
|
+
exit_code=2,
|
|
204
|
+
)
|
|
205
|
+
if unversioned and not selected_splits:
|
|
206
|
+
raise datasets._fail(
|
|
207
|
+
"--unversioned requires at least one --split",
|
|
208
|
+
"Repeat --split for each version-less split to register",
|
|
209
|
+
exit_code=2,
|
|
210
|
+
)
|
|
211
|
+
if owner is not None and owner != "@me":
|
|
212
|
+
raise datasets._fail(
|
|
213
|
+
"--owner only supports @me",
|
|
214
|
+
"Use --owner @me or omit --owner to leave ownership unchanged",
|
|
215
|
+
exit_code=2,
|
|
216
|
+
)
|
|
217
|
+
if benchmark and owner != "@me":
|
|
218
|
+
raise datasets._fail(
|
|
219
|
+
"--benchmark requires --owner @me because it is only used during create or claim",
|
|
220
|
+
"Add --owner @me or omit --benchmark",
|
|
221
|
+
exit_code=2,
|
|
222
|
+
)
|
|
223
|
+
if not math.isfinite(timeout) or timeout <= 0:
|
|
224
|
+
raise datasets._fail(
|
|
225
|
+
"--timeout must be a positive finite number",
|
|
226
|
+
"Pass a positive number of seconds",
|
|
227
|
+
exit_code=2,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
env = datasets._environment(environment)
|
|
231
|
+
repo = datasets._dataset_repo()
|
|
232
|
+
datasets._check_storage_side(repo, env)
|
|
233
|
+
ingest = getattr(getattr(repo, "versions", None), "ingest", None)
|
|
234
|
+
if not callable(ingest):
|
|
235
|
+
raise datasets._fail(
|
|
236
|
+
"The installed Instance Repo SDK does not support dataset ingest",
|
|
237
|
+
"Install or upgrade ap-client[dataset] to a version with versions.ingest",
|
|
238
|
+
exit_code=2,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
kwargs = {
|
|
242
|
+
"splits": selected_splits or None,
|
|
243
|
+
**datasets._env_kwargs(env),
|
|
244
|
+
"allow_layout_inference": selected_version is None and not selected_splits,
|
|
245
|
+
"dry_run": True,
|
|
246
|
+
"wait": False,
|
|
247
|
+
"timeout_sec": timeout,
|
|
248
|
+
"raise_on_failure": False,
|
|
249
|
+
}
|
|
250
|
+
try:
|
|
251
|
+
preview = datasets._model_dict(ingest(dataset, selected_version, **kwargs))
|
|
252
|
+
except Exception as exc: # noqa: BLE001 - SDK failures use the CLI contract
|
|
253
|
+
raise datasets._fail(
|
|
254
|
+
f"Failed to preview registration for dataset {dataset}: {_reason(exc)}",
|
|
255
|
+
"Check the dataset scope, cluster storage, and environment, then retry",
|
|
256
|
+
) from exc
|
|
257
|
+
|
|
258
|
+
preview_payload = _payload("preview", dataset, preview)
|
|
259
|
+
if dry_run:
|
|
260
|
+
datasets._emit_detail(preview_payload, fmt)
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
_emit_preview_stderr(preview_payload, fmt)
|
|
264
|
+
_confirm(dataset, yes=yes)
|
|
265
|
+
if owner == "@me":
|
|
266
|
+
_ensure_current_user_owns(repo, dataset, env, benchmark)
|
|
267
|
+
|
|
268
|
+
submission_kwargs = {**kwargs, "dry_run": False, "wait": wait}
|
|
269
|
+
if wait:
|
|
270
|
+
typer.echo(
|
|
271
|
+
f"Waiting up to {timeout:g} seconds for the registration task to finish...",
|
|
272
|
+
err=True,
|
|
273
|
+
)
|
|
274
|
+
try:
|
|
275
|
+
result = datasets._model_dict(ingest(dataset, selected_version, **submission_kwargs))
|
|
276
|
+
except Exception as exc: # noqa: BLE001 - SDK failures use the CLI contract
|
|
277
|
+
if wait:
|
|
278
|
+
failed_job = datasets._model_dict(getattr(exc, "job", None))
|
|
279
|
+
ingest_id = str(failed_job.get("ingest_id") or failed_job.get("id") or "").strip()
|
|
280
|
+
task_hint = f" task {ingest_id}" if ingest_id else " the task"
|
|
281
|
+
raise datasets._fail(
|
|
282
|
+
f"Failed to submit or wait for registration of dataset {dataset}: {_reason(exc)}",
|
|
283
|
+
f"The request may have been accepted and{task_hint} may still be running; "
|
|
284
|
+
"inspect dataset metadata before deciding whether to submit again",
|
|
285
|
+
) from exc
|
|
286
|
+
raise datasets._fail(
|
|
287
|
+
f"Failed to submit registration for dataset {dataset}: {_reason(exc)}",
|
|
288
|
+
"The preview did not write metadata; inspect the error and retry the command",
|
|
289
|
+
) from exc
|
|
290
|
+
|
|
291
|
+
status = str(result.get("status") or "").strip().lower()
|
|
292
|
+
if status in _TERMINAL_SUCCESS:
|
|
293
|
+
outcome = "completed"
|
|
294
|
+
elif status in _TERMINAL_FAILURE:
|
|
295
|
+
outcome = "failed"
|
|
296
|
+
elif status in _TERMINAL_CANCELED:
|
|
297
|
+
outcome = "canceled"
|
|
298
|
+
elif wait:
|
|
299
|
+
outcome = "timed_out"
|
|
300
|
+
else:
|
|
301
|
+
outcome = "submitted"
|
|
302
|
+
|
|
303
|
+
datasets._emit_detail(_payload(outcome, dataset, result), fmt)
|
|
304
|
+
ingest_id = str(result.get("ingest_id") or result.get("id") or "").strip()
|
|
305
|
+
if outcome == "failed":
|
|
306
|
+
raise datasets._fail(
|
|
307
|
+
f"Registration task {ingest_id or '<unknown>'} failed: "
|
|
308
|
+
f"{result.get('error_message') or ''}",
|
|
309
|
+
"Fix the reported data or metadata issue, then run the same registration command again",
|
|
310
|
+
)
|
|
311
|
+
if outcome == "canceled":
|
|
312
|
+
raise datasets._fail(
|
|
313
|
+
f"Registration task {ingest_id or '<unknown>'} was canceled",
|
|
314
|
+
"Inspect the dataset metadata before deciding whether to submit again",
|
|
315
|
+
)
|
|
316
|
+
if outcome == "timed_out":
|
|
317
|
+
raise datasets._fail(
|
|
318
|
+
"Registration wait timed out; the background task continues running",
|
|
319
|
+
f"Inspect dataset metadata after task {ingest_id or '<unknown>'} finishes",
|
|
320
|
+
)
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
错误);SDK 抛出的业务异常 → ``sdk_error_exit(exc, what=..., stage=...)``。
|
|
18
18
|
|
|
19
19
|
渲染与退出码沿用 ``ap_client.cli`` 的既有 helper,同样函数内部 import 以避免与
|
|
20
|
-
``cli``
|
|
20
|
+
``cli`` 循环依赖。默认列:``INSTANCE SPLIT VERSION DIGEST UPDATED_AT REGISTERED``。
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
from __future__ import annotations
|
|
@@ -52,6 +52,7 @@ _INSTANCE_COLUMNS: tuple[tuple[str, str], ...] = (
|
|
|
52
52
|
("version", "VERSION"),
|
|
53
53
|
("digest", "DIGEST"),
|
|
54
54
|
("updated_at", "UPDATED_AT"),
|
|
55
|
+
("registered", "REGISTERED"),
|
|
55
56
|
)
|
|
56
57
|
|
|
57
58
|
_FORMAT_HELP = "Output format: plain/table/json/yaml (default: AP_FORMAT or command default)"
|
|
@@ -292,6 +293,7 @@ def _instance_record(instance: Any) -> dict:
|
|
|
292
293
|
"manifest": served.get("manifest") or [],
|
|
293
294
|
"created_at": served.get("created_at"),
|
|
294
295
|
"updated_at": served.get("updated_at"),
|
|
296
|
+
"registered": served.get("registered", True),
|
|
295
297
|
}
|
|
296
298
|
if split and split != "default": # SDK 的 default 哨兵 = 无 split,服务端不返回该键
|
|
297
299
|
record["split"] = split
|
|
@@ -309,6 +311,7 @@ def _instance_row(record: dict) -> dict:
|
|
|
309
311
|
"version": record.get("dataset_version") or record.get("version") or "",
|
|
310
312
|
"digest": _record_digest(record),
|
|
311
313
|
"updated_at": record.get("updated_at") or "",
|
|
314
|
+
"registered": record.get("registered", True),
|
|
312
315
|
}
|
|
313
316
|
|
|
314
317
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ap-client"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.1.dev0"
|
|
8
8
|
description = "Agent Platform API Client & CLI"
|
|
9
9
|
readme = { text = "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.", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -18,10 +18,10 @@ dependencies = [
|
|
|
18
18
|
|
|
19
19
|
[project.optional-dependencies]
|
|
20
20
|
dataset = [
|
|
21
|
-
"instance-repo[oss]
|
|
21
|
+
"instance-repo[oss]==1.3.2.dev0",
|
|
22
22
|
]
|
|
23
23
|
all = [
|
|
24
|
-
"instance-repo[oss]
|
|
24
|
+
"instance-repo[oss]==1.3.2.dev0",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.scripts]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|