ap-client 0.2.2.dev1__tar.gz → 0.3.0__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.2.2.dev1 → ap_client-0.3.0}/PKG-INFO +3 -3
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/ap_client/api.py +7 -254
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/ap_client/cli.py +51 -25
- ap_client-0.3.0/ap_client/irepo_commands.py +131 -0
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/ap_client/profile_commands.py +2 -1
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/ap_client/waiter.py +1 -1
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/pyproject.toml +3 -3
- ap_client-0.2.2.dev1/ap_client/dataset_commands.py +0 -1381
- ap_client-0.2.2.dev1/ap_client/fs_commands.py +0 -674
- ap_client-0.2.2.dev1/ap_client/instance_commands.py +0 -551
- ap_client-0.2.2.dev1/ap_client/irepo_sdk.py +0 -326
- ap_client-0.2.2.dev1/ap_client/split_publish.py +0 -133
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/ap_client/__init__.py +0 -0
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/ap_client/config.py +0 -0
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/ap_client/exporter.py +0 -0
- {ap_client-0.2.2.dev1 → ap_client-0.3.0}/ap_client/tbb.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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]>=0.7.0; extra == 'all'
|
|
13
13
|
Provides-Extra: dataset
|
|
14
|
-
Requires-Dist: instance-repo[oss]>=
|
|
14
|
+
Requires-Dist: instance-repo[oss]>=0.7.0; 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.
|
|
@@ -293,48 +293,6 @@ def _secret_ws_params(workspace_id: Optional[str]) -> Optional[dict]:
|
|
|
293
293
|
return {"workspace_id": workspace_id} if workspace_id else None
|
|
294
294
|
|
|
295
295
|
|
|
296
|
-
def _unwrap_data(payload: Any) -> Any:
|
|
297
|
-
"""Unwrap the apiserver ``{code,message,data}`` envelope when present.
|
|
298
|
-
|
|
299
|
-
The dataset-domain detail/create/patch endpoints return a bare JSON body,
|
|
300
|
-
but the same handlers are occasionally wrapped by the generic success
|
|
301
|
-
envelope. Tolerate both so command code never has to branch: a mapping that
|
|
302
|
-
carries ``data`` alongside ``code``/``message`` is treated as an envelope,
|
|
303
|
-
anything else is returned verbatim.
|
|
304
|
-
"""
|
|
305
|
-
if (
|
|
306
|
-
isinstance(payload, dict)
|
|
307
|
-
and "data" in payload
|
|
308
|
-
and ("code" in payload or "message" in payload)
|
|
309
|
-
):
|
|
310
|
-
return payload["data"]
|
|
311
|
-
return payload
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
def _split_paged(payload: Any) -> tuple[list[dict], dict]:
|
|
315
|
-
"""Split a ``PagedSuccess`` envelope into ``(records, pagination)``.
|
|
316
|
-
|
|
317
|
-
``PagedSuccess`` is ``{code,message,data:[...],pagination:{total,page,page_size}}``.
|
|
318
|
-
A bare list (or a null ``data``) yields an empty pagination dict so callers
|
|
319
|
-
can render results without probing the response shape.
|
|
320
|
-
"""
|
|
321
|
-
if isinstance(payload, list):
|
|
322
|
-
return [item for item in payload if isinstance(item, dict)], {}
|
|
323
|
-
if not isinstance(payload, dict):
|
|
324
|
-
return [], {}
|
|
325
|
-
records = payload.get("data")
|
|
326
|
-
pagination = payload.get("pagination")
|
|
327
|
-
return (
|
|
328
|
-
[item for item in (records or []) if isinstance(item, dict)],
|
|
329
|
-
pagination if isinstance(pagination, dict) else {},
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
def _as_record_list(payload: Any) -> list[dict]:
|
|
334
|
-
"""Coerce a list-returning endpoint to ``list[dict]``, envelope or not."""
|
|
335
|
-
return _split_paged(payload)[0]
|
|
336
|
-
|
|
337
|
-
|
|
338
296
|
class APIClient:
|
|
339
297
|
"""Agent Platform API client."""
|
|
340
298
|
|
|
@@ -750,35 +708,8 @@ class APIClient:
|
|
|
750
708
|
return self._get(f"/templates/{quote(name)}", params=params)
|
|
751
709
|
|
|
752
710
|
def list_benchmarks(self) -> list:
|
|
753
|
-
"""List all benchmarks from the
|
|
754
|
-
benchmarks
|
|
755
|
-
page = 1
|
|
756
|
-
page_size = 200
|
|
757
|
-
# 与 CLI dataset list 的自动翻页同口径:100 页上限,防分页异常导致死循环。
|
|
758
|
-
max_pages = 100
|
|
759
|
-
while page <= max_pages:
|
|
760
|
-
payload = self._get(
|
|
761
|
-
"/apis/v1/benchmarks", params={"page": page, "page_size": page_size}
|
|
762
|
-
)
|
|
763
|
-
records, pagination = _split_paged(payload)
|
|
764
|
-
for record in records:
|
|
765
|
-
item = dict(record)
|
|
766
|
-
if "source_id" in item:
|
|
767
|
-
item.setdefault("id", item["source_id"])
|
|
768
|
-
benchmarks.append(item)
|
|
769
|
-
if not records or not pagination:
|
|
770
|
-
break
|
|
771
|
-
try:
|
|
772
|
-
total = int(pagination.get("total"))
|
|
773
|
-
except (TypeError, ValueError):
|
|
774
|
-
total = None
|
|
775
|
-
if total is not None:
|
|
776
|
-
if len(benchmarks) >= total:
|
|
777
|
-
break
|
|
778
|
-
elif len(records) < page_size:
|
|
779
|
-
break
|
|
780
|
-
page += 1
|
|
781
|
-
return benchmarks
|
|
711
|
+
"""List all active benchmarks from the local benchmark registry."""
|
|
712
|
+
return self._get("/benchmarks")
|
|
782
713
|
|
|
783
714
|
def get_benchmark(self, name: str) -> dict:
|
|
784
715
|
"""Get a single benchmark by exact name."""
|
|
@@ -913,189 +844,6 @@ class APIClient:
|
|
|
913
844
|
"instance_ids": all_instance_ids,
|
|
914
845
|
}
|
|
915
846
|
|
|
916
|
-
# ==================== Dataset series (apiserver dataset domain) ====================
|
|
917
|
-
#
|
|
918
|
-
# 这一节对接 Go apiserver 的 dataset 领域(/apis/v1/datasets/...),与上面
|
|
919
|
-
# ossdata 目录的 /api/datasets 路由完全无关。方法名统一带 `_series`,
|
|
920
|
-
# 以免和 `ap job create --dataset` 依赖的 list_all_datasets /
|
|
921
|
-
# list_dataset_versions / list_all_dataset_instances 混淆。
|
|
922
|
-
#
|
|
923
|
-
# 列表端点返回 PagedSuccess envelope:
|
|
924
|
-
# {"code":0,"message":"ok","data":[...],"pagination":{"total","page","page_size"}}
|
|
925
|
-
# 详情/创建/更新端点返回裸 JSON body。
|
|
926
|
-
|
|
927
|
-
_DATASET_SERIES_BASE = "/apis/v1/datasets/series"
|
|
928
|
-
_DATASET_VERSIONS_BASE = "/apis/v1/datasets/versions"
|
|
929
|
-
_DATASET_INSTANCES_BASE = "/apis/v1/datasets/instances"
|
|
930
|
-
|
|
931
|
-
def list_dataset_series_page(
|
|
932
|
-
self,
|
|
933
|
-
*,
|
|
934
|
-
q: Optional[str] = None,
|
|
935
|
-
keyword: Optional[str] = None,
|
|
936
|
-
visibility: Optional[str] = None,
|
|
937
|
-
benchmark_id: Optional[str] = None,
|
|
938
|
-
include_deprecated: bool = False,
|
|
939
|
-
environment: Optional[str] = None,
|
|
940
|
-
page: int = 1,
|
|
941
|
-
page_size: int = 20,
|
|
942
|
-
) -> dict:
|
|
943
|
-
"""一页 dataset series(原始 PagedSuccess envelope)。
|
|
944
|
-
|
|
945
|
-
服务端没有 owner/mine/l1 过滤参数,调用方需要时只能在客户端做页内过滤。
|
|
946
|
-
"""
|
|
947
|
-
params: dict = {"page": page, "page_size": page_size}
|
|
948
|
-
if q:
|
|
949
|
-
params["q"] = q
|
|
950
|
-
if keyword:
|
|
951
|
-
params["keyword"] = keyword
|
|
952
|
-
if visibility:
|
|
953
|
-
params["visibility"] = visibility
|
|
954
|
-
if benchmark_id:
|
|
955
|
-
params["benchmark_id"] = benchmark_id
|
|
956
|
-
if include_deprecated:
|
|
957
|
-
params["include_deprecated"] = "true"
|
|
958
|
-
if environment:
|
|
959
|
-
params["environment"] = environment
|
|
960
|
-
return self._get(self._DATASET_SERIES_BASE, params=params)
|
|
961
|
-
|
|
962
|
-
def list_dataset_series(self, **kw) -> tuple[list[dict], dict]:
|
|
963
|
-
"""列出 dataset series,返回 ``(records, pagination)``。"""
|
|
964
|
-
return _split_paged(self.list_dataset_series_page(**kw))
|
|
965
|
-
|
|
966
|
-
def get_dataset_series(self, dataset_name: str, *, environment: Optional[str] = None) -> dict:
|
|
967
|
-
"""按名字取 dataset series 详情(含 dataset_id / claimable / stats)。"""
|
|
968
|
-
params: dict = {"dataset_name": dataset_name}
|
|
969
|
-
if environment:
|
|
970
|
-
params["environment"] = environment
|
|
971
|
-
return _unwrap_data(self._get(f"{self._DATASET_SERIES_BASE}/detail", params=params))
|
|
972
|
-
|
|
973
|
-
def update_dataset_series(self, dataset_name: str, body: dict) -> dict:
|
|
974
|
-
"""PATCH dataset series 元数据(visibility/status 需要 dataset-admin)。"""
|
|
975
|
-
return _unwrap_data(
|
|
976
|
-
self._request(
|
|
977
|
-
"PATCH",
|
|
978
|
-
f"{self._DATASET_SERIES_BASE}/detail",
|
|
979
|
-
params={"dataset_name": dataset_name},
|
|
980
|
-
json_body=body,
|
|
981
|
-
)
|
|
982
|
-
)
|
|
983
|
-
|
|
984
|
-
def list_claim_workspaces(self) -> list[dict]:
|
|
985
|
-
"""列出当前用户可用于认领的 workspace。"""
|
|
986
|
-
return _as_record_list(self._get("/apis/v1/datasets/claim-workspaces"))
|
|
987
|
-
|
|
988
|
-
# ---- dataset versions ----
|
|
989
|
-
|
|
990
|
-
def list_dataset_series_versions(
|
|
991
|
-
self,
|
|
992
|
-
dataset_name: str,
|
|
993
|
-
*,
|
|
994
|
-
status: Optional[str] = None,
|
|
995
|
-
page: int = 1,
|
|
996
|
-
page_size: int = 20,
|
|
997
|
-
environment: Optional[str] = None,
|
|
998
|
-
) -> tuple[list[dict], dict]:
|
|
999
|
-
"""列出某 dataset 的版本,返回 ``(records, pagination)``。列表项不含 splits 数组。"""
|
|
1000
|
-
params: dict = {"dataset_name": dataset_name, "page": page, "page_size": page_size}
|
|
1001
|
-
if status:
|
|
1002
|
-
params["status"] = status
|
|
1003
|
-
if environment:
|
|
1004
|
-
params["environment"] = environment
|
|
1005
|
-
return _split_paged(self._get(self._DATASET_VERSIONS_BASE, params=params))
|
|
1006
|
-
|
|
1007
|
-
def get_dataset_series_version(
|
|
1008
|
-
self,
|
|
1009
|
-
dataset_name: str,
|
|
1010
|
-
version: str,
|
|
1011
|
-
*,
|
|
1012
|
-
environment: Optional[str] = None,
|
|
1013
|
-
) -> dict:
|
|
1014
|
-
"""取单个版本详情(含 run_type / splits[] / manifest / published_at)。
|
|
1015
|
-
|
|
1016
|
-
``version=""`` 表示 split_first 的**无版本空间**,原样下发空串——绝不隐式取 latest。
|
|
1017
|
-
"""
|
|
1018
|
-
params: dict = {"dataset_name": dataset_name, "version": version}
|
|
1019
|
-
if environment:
|
|
1020
|
-
params["environment"] = environment
|
|
1021
|
-
return _unwrap_data(self._get(f"{self._DATASET_VERSIONS_BASE}/detail", params=params))
|
|
1022
|
-
|
|
1023
|
-
# ---- dataset instances (metadata only) ----
|
|
1024
|
-
|
|
1025
|
-
def list_dataset_series_instances(
|
|
1026
|
-
self,
|
|
1027
|
-
dataset_name: str,
|
|
1028
|
-
*,
|
|
1029
|
-
version: Optional[str] = None,
|
|
1030
|
-
split: Optional[str] = None,
|
|
1031
|
-
instance_id: Optional[str] = None,
|
|
1032
|
-
page: int = 1,
|
|
1033
|
-
page_size: int = 50,
|
|
1034
|
-
environment: Optional[str] = None,
|
|
1035
|
-
) -> tuple[list[dict], dict]:
|
|
1036
|
-
"""列出 instance 元数据,返回 ``(records, pagination)``。
|
|
1037
|
-
|
|
1038
|
-
``version=""`` / ``split=""`` 原样下发(无版本空间语义),``None`` 才省略该参数。
|
|
1039
|
-
"""
|
|
1040
|
-
params: dict = {"dataset_name": dataset_name, "page": page, "page_size": page_size}
|
|
1041
|
-
if version is not None:
|
|
1042
|
-
params["version"] = version
|
|
1043
|
-
if split is not None:
|
|
1044
|
-
params["split"] = split
|
|
1045
|
-
if instance_id:
|
|
1046
|
-
params["instance_id"] = instance_id
|
|
1047
|
-
if environment:
|
|
1048
|
-
params["environment"] = environment
|
|
1049
|
-
return _split_paged(self._get(self._DATASET_INSTANCES_BASE, params=params))
|
|
1050
|
-
|
|
1051
|
-
def get_dataset_series_instance(
|
|
1052
|
-
self,
|
|
1053
|
-
dataset_name: str,
|
|
1054
|
-
version: str,
|
|
1055
|
-
split: str,
|
|
1056
|
-
instance_id: str,
|
|
1057
|
-
*,
|
|
1058
|
-
environment: Optional[str] = None,
|
|
1059
|
-
) -> dict:
|
|
1060
|
-
"""取单个 instance 的元数据详情。``version=""`` 表示无版本空间。"""
|
|
1061
|
-
params: dict = {
|
|
1062
|
-
"dataset_name": dataset_name,
|
|
1063
|
-
"version": version,
|
|
1064
|
-
"split": split,
|
|
1065
|
-
"instance_id": instance_id,
|
|
1066
|
-
}
|
|
1067
|
-
if environment:
|
|
1068
|
-
params["environment"] = environment
|
|
1069
|
-
return _unwrap_data(self._get(f"{self._DATASET_INSTANCES_BASE}/detail", params=params))
|
|
1070
|
-
|
|
1071
|
-
def get_dataset_series_split(self, dataset_name: str, version: str, split: str) -> dict:
|
|
1072
|
-
"""Read the selected split's lifecycle without a version-level aggregate."""
|
|
1073
|
-
return _unwrap_data(
|
|
1074
|
-
self._get(
|
|
1075
|
-
"/apis/v1/datasets/splits/detail",
|
|
1076
|
-
params={"dataset_name": dataset_name, "version": version, "split": split},
|
|
1077
|
-
)
|
|
1078
|
-
)
|
|
1079
|
-
|
|
1080
|
-
def get_dataset_split_release(self, workflow_id: str) -> dict:
|
|
1081
|
-
"""Read a release workflow, including approval and per-step outcomes."""
|
|
1082
|
-
return _unwrap_data(self._get(f"/apis/v1/workflows/{quote(workflow_id, safe='')}"))
|
|
1083
|
-
|
|
1084
|
-
# ---- permissions ----
|
|
1085
|
-
|
|
1086
|
-
def get_my_resource_permissions(self, resource_type: str, resource_id: str) -> list[str]:
|
|
1087
|
-
"""当前用户在某资源上的 action 列表(``{"actions":[...]}``)。"""
|
|
1088
|
-
payload = self._get(
|
|
1089
|
-
"/apis/v1/me/resource-permissions",
|
|
1090
|
-
params={"resource_type": resource_type, "resource_id": resource_id},
|
|
1091
|
-
)
|
|
1092
|
-
payload = _unwrap_data(payload)
|
|
1093
|
-
if isinstance(payload, dict):
|
|
1094
|
-
actions = payload.get("actions")
|
|
1095
|
-
if isinstance(actions, list):
|
|
1096
|
-
return [str(action) for action in actions]
|
|
1097
|
-
return []
|
|
1098
|
-
|
|
1099
847
|
# ==================== Meta operations ====================
|
|
1100
848
|
|
|
1101
849
|
def list_meta_models(self) -> dict:
|
|
@@ -1243,6 +991,7 @@ class APIClient:
|
|
|
1243
991
|
runner: Optional[str] = None,
|
|
1244
992
|
runner_options: Optional[dict] = None,
|
|
1245
993
|
idempotency_key: Optional[str] = None,
|
|
994
|
+
max_failure_retries: int = 0,
|
|
1246
995
|
# ─── Scheduling hints ─────────────────────────────────────────
|
|
1247
996
|
priority: Optional[str] = None,
|
|
1248
997
|
priority_weight: Optional[int] = None,
|
|
@@ -1295,6 +1044,7 @@ class APIClient:
|
|
|
1295
1044
|
runner=runner,
|
|
1296
1045
|
runner_options=runner_options,
|
|
1297
1046
|
idempotency_key=idempotency_key,
|
|
1047
|
+
max_failure_retries=max_failure_retries,
|
|
1298
1048
|
priority=priority,
|
|
1299
1049
|
priority_weight=priority_weight,
|
|
1300
1050
|
resource_mode=resource_mode,
|
|
@@ -1345,6 +1095,7 @@ class APIClient:
|
|
|
1345
1095
|
runner: Optional[str] = None,
|
|
1346
1096
|
runner_options: Optional[dict] = None,
|
|
1347
1097
|
idempotency_key: Optional[str] = None,
|
|
1098
|
+
max_failure_retries: int = 0,
|
|
1348
1099
|
# ─── Scheduling hints ─────────────────────────────────────────
|
|
1349
1100
|
priority: Optional[str] = None,
|
|
1350
1101
|
priority_weight: Optional[int] = None,
|
|
@@ -1431,6 +1182,8 @@ class APIClient:
|
|
|
1431
1182
|
body["runner_options"] = runner_options
|
|
1432
1183
|
if idempotency_key is not None:
|
|
1433
1184
|
body["idempotency_key"] = idempotency_key
|
|
1185
|
+
if max_failure_retries:
|
|
1186
|
+
body["max_failure_retries"] = max_failure_retries
|
|
1434
1187
|
|
|
1435
1188
|
# Scheduling hints
|
|
1436
1189
|
if priority is not None:
|
|
@@ -22,10 +22,8 @@ from ap_client.api import (
|
|
|
22
22
|
set_verbose_override,
|
|
23
23
|
)
|
|
24
24
|
from ap_client.config import ENV_VAR_SPECS, ConfigurationError, _parse_bool, normalize_output_format
|
|
25
|
-
from ap_client.dataset_commands import register as _register_dataset_commands
|
|
26
25
|
from ap_client.exporter import export_group, export_job
|
|
27
|
-
from ap_client.
|
|
28
|
-
from ap_client.instance_commands import register as _register_instance_commands
|
|
26
|
+
from ap_client.irepo_commands import register as _register_dataset_repo
|
|
29
27
|
from ap_client.profile_commands import profile_app
|
|
30
28
|
from ap_client.waiter import (
|
|
31
29
|
WaitTimeoutError,
|
|
@@ -146,11 +144,8 @@ app.add_typer(meta_job_type_app, name="meta-job-type")
|
|
|
146
144
|
app.add_typer(benchmark_app, name="benchmark")
|
|
147
145
|
app.add_typer(checkpoint_app, name="checkpoint")
|
|
148
146
|
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
_register_dataset_commands(dataset_app)
|
|
152
|
-
_register_instance_commands(app)
|
|
153
|
-
_register_fs_commands(app)
|
|
147
|
+
# ap dataset repo:透传 instance_repo CLI(见 ap_client/irepo_commands.py)
|
|
148
|
+
_register_dataset_repo(dataset_app)
|
|
154
149
|
|
|
155
150
|
_PAI_RUNTIME_ENV_TAGS: tuple[tuple[str, str], ...] = (
|
|
156
151
|
("DLC_JOB_ID", "dlc_job_id"),
|
|
@@ -210,6 +205,7 @@ def _build_job_create_retry_command(
|
|
|
210
205
|
concurrency: Optional[int],
|
|
211
206
|
batch_size: int,
|
|
212
207
|
trials: Optional[int] = None,
|
|
208
|
+
max_failure_retries: int = 0,
|
|
213
209
|
wait: bool,
|
|
214
210
|
wait_interval: float,
|
|
215
211
|
wait_timeout: Optional[float],
|
|
@@ -261,6 +257,8 @@ def _build_job_create_retry_command(
|
|
|
261
257
|
args.extend(["--batch-size", str(batch_size)])
|
|
262
258
|
if trials is not None:
|
|
263
259
|
args.extend(["--trials", str(trials)])
|
|
260
|
+
if max_failure_retries:
|
|
261
|
+
args.extend(["--max-failure-retries", str(max_failure_retries)])
|
|
264
262
|
if wait:
|
|
265
263
|
args.append("--wait")
|
|
266
264
|
if wait_interval != 5.0:
|
|
@@ -929,7 +927,10 @@ def _print_params_plain(params: Any, masked_params: list) -> None:
|
|
|
929
927
|
rows: list[tuple[str, Any]] = []
|
|
930
928
|
for key, value in params.items():
|
|
931
929
|
display_value = _format_plain_value(value)
|
|
932
|
-
|
|
930
|
+
# 直接信任 masked_params 路径:服务端掩码形态已扩展为保留首尾(如
|
|
931
|
+
# sk-***890 / ****),不能再按值严格等于 "***" 判断,否则形态变化后
|
|
932
|
+
# (masked) 标识静默丢失
|
|
933
|
+
if str(key) in masked_paths:
|
|
933
934
|
display_value = f"{display_value} (masked)"
|
|
934
935
|
rows.append((str(key), display_value))
|
|
935
936
|
_print_key_values(rows, skip_empty=False)
|
|
@@ -1083,6 +1084,7 @@ def _idempotency_item_key(
|
|
|
1083
1084
|
queue: Optional[str],
|
|
1084
1085
|
account_pool: Optional[str],
|
|
1085
1086
|
resource_profile_id: Optional[str] = None,
|
|
1087
|
+
max_failure_retries: int = 0,
|
|
1086
1088
|
) -> str:
|
|
1087
1089
|
# Client-supplied item keys are authoritative; server hashing is only a
|
|
1088
1090
|
# fallback for non-CLI callers, so this payload intentionally need not
|
|
@@ -1102,6 +1104,8 @@ def _idempotency_item_key(
|
|
|
1102
1104
|
}
|
|
1103
1105
|
if resource_profile_id is not None:
|
|
1104
1106
|
payload_data["resource_profile_id"] = resource_profile_id
|
|
1107
|
+
if max_failure_retries:
|
|
1108
|
+
payload_data["max_failure_retries"] = max_failure_retries
|
|
1105
1109
|
payload = json.dumps(
|
|
1106
1110
|
payload_data,
|
|
1107
1111
|
sort_keys=True,
|
|
@@ -2020,17 +2024,24 @@ def template_fetch(
|
|
|
2020
2024
|
|
|
2021
2025
|
# ==================== Dataset operations ====================
|
|
2022
2026
|
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2027
|
+
|
|
2028
|
+
@dataset_app.command("list")
|
|
2029
|
+
def dataset_list(
|
|
2030
|
+
search: Optional[str] = typer.Argument(None, help="Search keyword"),
|
|
2031
|
+
output_format: str = typer.Option(
|
|
2032
|
+
None,
|
|
2033
|
+
"--format",
|
|
2034
|
+
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
2035
|
+
),
|
|
2036
|
+
):
|
|
2037
|
+
"""List all datasets."""
|
|
2038
|
+
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
2039
|
+
client = get_client()
|
|
2040
|
+
result = client.list_all_datasets(search)
|
|
2041
|
+
if output_format == "table":
|
|
2042
|
+
_print_records_table(result)
|
|
2043
|
+
else:
|
|
2044
|
+
_print_formatted(result, output_format)
|
|
2034
2045
|
|
|
2035
2046
|
|
|
2036
2047
|
@dataset_app.command("versions")
|
|
@@ -2042,9 +2053,8 @@ def dataset_versions(
|
|
|
2042
2053
|
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
2043
2054
|
),
|
|
2044
2055
|
):
|
|
2045
|
-
"""List dataset versions
|
|
2056
|
+
"""List dataset versions."""
|
|
2046
2057
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
2047
|
-
_emit_progress(_DATASET_VERSIONS_DEPRECATION)
|
|
2048
2058
|
client = get_client()
|
|
2049
2059
|
versions = client.list_dataset_versions(dataset)
|
|
2050
2060
|
if output_format == "table":
|
|
@@ -2064,9 +2074,8 @@ def dataset_instances(
|
|
|
2064
2074
|
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
2065
2075
|
),
|
|
2066
2076
|
):
|
|
2067
|
-
"""List dataset instances
|
|
2077
|
+
"""List dataset instances."""
|
|
2068
2078
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
2069
|
-
_emit_progress(_DATASET_INSTANCES_DEPRECATION)
|
|
2070
2079
|
client = get_client()
|
|
2071
2080
|
result = client.list_all_dataset_instances(dataset_version)
|
|
2072
2081
|
if output_format == "table":
|
|
@@ -2886,6 +2895,12 @@ def job_create(
|
|
|
2886
2895
|
"--idempotency-key",
|
|
2887
2896
|
help="UUID4 key for retrying the same logical submission; providing one enables idempotency",
|
|
2888
2897
|
),
|
|
2898
|
+
max_failure_retries: int = typer.Option(
|
|
2899
|
+
0,
|
|
2900
|
+
"--max-failure-retries",
|
|
2901
|
+
min=0,
|
|
2902
|
+
help="Maximum automatic retries after each Job fails (default: 0)",
|
|
2903
|
+
),
|
|
2889
2904
|
dry_run: bool = typer.Option(
|
|
2890
2905
|
False,
|
|
2891
2906
|
"--dry-run",
|
|
@@ -3227,6 +3242,7 @@ def job_create(
|
|
|
3227
3242
|
runner=runner,
|
|
3228
3243
|
runner_options=runner_options_dict,
|
|
3229
3244
|
idempotency_key=submission_idempotency_key,
|
|
3245
|
+
max_failure_retries=max_failure_retries,
|
|
3230
3246
|
group_id=group_id,
|
|
3231
3247
|
model_base_url_collection=model_base_url_collection_list,
|
|
3232
3248
|
group_post_process=group_post_process_dict,
|
|
@@ -3281,6 +3297,7 @@ def job_create(
|
|
|
3281
3297
|
concurrency=concurrency,
|
|
3282
3298
|
batch_size=batch_size,
|
|
3283
3299
|
trials=trials,
|
|
3300
|
+
max_failure_retries=max_failure_retries,
|
|
3284
3301
|
wait=wait,
|
|
3285
3302
|
wait_interval=wait_interval,
|
|
3286
3303
|
wait_timeout=wait_timeout,
|
|
@@ -3328,6 +3345,7 @@ def job_create(
|
|
|
3328
3345
|
runner=runner,
|
|
3329
3346
|
runner_options=runner_options_dict,
|
|
3330
3347
|
idempotency_key=submission_idempotency_key,
|
|
3348
|
+
max_failure_retries=max_failure_retries,
|
|
3331
3349
|
group_id=group_id,
|
|
3332
3350
|
model_base_url_collection=model_base_url_collection_list,
|
|
3333
3351
|
priority=priority,
|
|
@@ -3521,6 +3539,7 @@ def job_create(
|
|
|
3521
3539
|
overrides=overrides,
|
|
3522
3540
|
concurrency=concurrency,
|
|
3523
3541
|
batch_size=batch_size,
|
|
3542
|
+
max_failure_retries=max_failure_retries,
|
|
3524
3543
|
wait=wait,
|
|
3525
3544
|
wait_interval=wait_interval,
|
|
3526
3545
|
wait_timeout=wait_timeout,
|
|
@@ -3630,6 +3649,7 @@ def job_create(
|
|
|
3630
3649
|
queue=queue,
|
|
3631
3650
|
account_pool=account_pool,
|
|
3632
3651
|
resource_profile_id=resource_profile_id,
|
|
3652
|
+
max_failure_retries=max_failure_retries,
|
|
3633
3653
|
)
|
|
3634
3654
|
batch.append(item)
|
|
3635
3655
|
return batch
|
|
@@ -3653,6 +3673,7 @@ def job_create(
|
|
|
3653
3673
|
enable_otel_tracing=enable_otel_tracing,
|
|
3654
3674
|
runner=runner,
|
|
3655
3675
|
runner_options=runner_options_dict,
|
|
3676
|
+
max_failure_retries=max_failure_retries,
|
|
3656
3677
|
priority=priority,
|
|
3657
3678
|
priority_weight=priority_weight,
|
|
3658
3679
|
resource_mode=resource_mode,
|
|
@@ -3694,6 +3715,7 @@ def job_create(
|
|
|
3694
3715
|
runner=runner,
|
|
3695
3716
|
runner_options=runner_options_dict,
|
|
3696
3717
|
idempotency_key=submission_idempotency_key,
|
|
3718
|
+
max_failure_retries=max_failure_retries,
|
|
3697
3719
|
priority=priority,
|
|
3698
3720
|
priority_weight=priority_weight,
|
|
3699
3721
|
resource_mode=resource_mode,
|
|
@@ -3749,6 +3771,7 @@ def job_create(
|
|
|
3749
3771
|
runner=runner,
|
|
3750
3772
|
runner_options=runner_options_dict,
|
|
3751
3773
|
idempotency_key=submission_idempotency_key,
|
|
3774
|
+
max_failure_retries=max_failure_retries,
|
|
3752
3775
|
priority=priority,
|
|
3753
3776
|
priority_weight=priority_weight,
|
|
3754
3777
|
resource_mode=resource_mode,
|
|
@@ -3843,6 +3866,7 @@ def job_create(
|
|
|
3843
3866
|
enable_otel_tracing=enable_otel_tracing,
|
|
3844
3867
|
runner=runner,
|
|
3845
3868
|
runner_options=runner_options_dict,
|
|
3869
|
+
max_failure_retries=max_failure_retries,
|
|
3846
3870
|
priority=priority,
|
|
3847
3871
|
priority_weight=priority_weight,
|
|
3848
3872
|
resource_mode=resource_mode,
|
|
@@ -3878,6 +3902,7 @@ def job_create(
|
|
|
3878
3902
|
runner=runner,
|
|
3879
3903
|
runner_options=runner_options_dict,
|
|
3880
3904
|
idempotency_key=submission_idempotency_key,
|
|
3905
|
+
max_failure_retries=max_failure_retries,
|
|
3881
3906
|
priority=priority,
|
|
3882
3907
|
priority_weight=priority_weight,
|
|
3883
3908
|
resource_mode=resource_mode,
|
|
@@ -3923,6 +3948,7 @@ def job_create(
|
|
|
3923
3948
|
runner=runner,
|
|
3924
3949
|
runner_options=runner_options_dict,
|
|
3925
3950
|
idempotency_key=submission_idempotency_key,
|
|
3951
|
+
max_failure_retries=max_failure_retries,
|
|
3926
3952
|
priority=priority,
|
|
3927
3953
|
priority_weight=priority_weight,
|
|
3928
3954
|
resource_mode=resource_mode,
|
|
@@ -4779,7 +4805,7 @@ def benchmark_list(
|
|
|
4779
4805
|
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
4780
4806
|
),
|
|
4781
4807
|
):
|
|
4782
|
-
"""List benchmarks from the
|
|
4808
|
+
"""List benchmarks from the local benchmark registry."""
|
|
4783
4809
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
4784
4810
|
client = get_client()
|
|
4785
4811
|
result = client.list_benchmarks()
|