ap-client 0.3.0.dev0__tar.gz → 0.3.1.dev2__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.3.0.dev0 → ap_client-0.3.1.dev2}/PKG-INFO +3 -3
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/ap_client/api.py +103 -9
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/ap_client/cli.py +210 -63
- ap_client-0.3.1.dev2/ap_client/dataset_commands.py +2028 -0
- ap_client-0.3.1.dev2/ap_client/fs_commands.py +676 -0
- ap_client-0.3.1.dev2/ap_client/instance_commands.py +729 -0
- ap_client-0.3.1.dev2/ap_client/irepo_sdk.py +371 -0
- ap_client-0.3.1.dev2/ap_client/managed_ak.py +86 -0
- ap_client-0.3.1.dev2/ap_client/split_publish.py +136 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/pyproject.toml +3 -3
- ap_client-0.3.0.dev0/ap_client/irepo_commands.py +0 -131
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/ap_client/__init__.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/ap_client/config.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/ap_client/exporter.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/ap_client/profile_commands.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/ap_client/tbb.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev2}/ap_client/waiter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1.dev2
|
|
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.1.0; extra == 'all'
|
|
13
13
|
Provides-Extra: dataset
|
|
14
|
-
Requires-Dist: instance-repo[oss]>=
|
|
14
|
+
Requires-Dist: instance-repo[oss]>=1.1.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.
|
|
@@ -15,6 +15,7 @@ from urllib.parse import parse_qsl, quote, urlencode, urlsplit, urlunsplit
|
|
|
15
15
|
import requests
|
|
16
16
|
|
|
17
17
|
from .config import Config, ConfigurationError, get_config, has_cluster_header
|
|
18
|
+
from .managed_ak import require_managed_ak_ack, validate_ak_id, validate_ak_selection
|
|
18
19
|
|
|
19
20
|
_DEFAULT_CONNECT_TIMEOUT = 300
|
|
20
21
|
_DEFAULT_READ_TIMEOUT = 86400
|
|
@@ -293,6 +294,25 @@ def _secret_ws_params(workspace_id: Optional[str]) -> Optional[dict]:
|
|
|
293
294
|
return {"workspace_id": workspace_id} if workspace_id else None
|
|
294
295
|
|
|
295
296
|
|
|
297
|
+
def _split_paged(payload: Any) -> tuple[list[dict], dict]:
|
|
298
|
+
"""Split a ``PagedSuccess`` envelope into ``(records, pagination)``.
|
|
299
|
+
|
|
300
|
+
``PagedSuccess`` is ``{code,message,data:[...],pagination:{total,page,page_size}}``.
|
|
301
|
+
A bare list (or a null ``data``) yields an empty pagination dict so callers
|
|
302
|
+
can render results without probing the response shape.
|
|
303
|
+
"""
|
|
304
|
+
if isinstance(payload, list):
|
|
305
|
+
return [item for item in payload if isinstance(item, dict)], {}
|
|
306
|
+
if not isinstance(payload, dict):
|
|
307
|
+
return [], {}
|
|
308
|
+
records = payload.get("data")
|
|
309
|
+
pagination = payload.get("pagination")
|
|
310
|
+
return (
|
|
311
|
+
[item for item in (records or []) if isinstance(item, dict)],
|
|
312
|
+
pagination if isinstance(pagination, dict) else {},
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
296
316
|
class APIClient:
|
|
297
317
|
"""Agent Platform API client."""
|
|
298
318
|
|
|
@@ -459,7 +479,12 @@ class APIClient:
|
|
|
459
479
|
|
|
460
480
|
def _post(self, path: str, data: Any, timeout: TimeoutType = None) -> Any:
|
|
461
481
|
"""Send a POST request."""
|
|
462
|
-
|
|
482
|
+
from .managed_ak import parameter_ak_ids, require_parameter_ak_ack
|
|
483
|
+
|
|
484
|
+
references = parameter_ak_ids(data) if path == "/jobs" else set()
|
|
485
|
+
result = self._request("POST", path, json_body=data, timeout=timeout)
|
|
486
|
+
require_parameter_ak_ack(result, references)
|
|
487
|
+
return result
|
|
463
488
|
|
|
464
489
|
def _delete(self, path: str, *, params: Optional[dict] = None) -> Any:
|
|
465
490
|
"""Send a DELETE request."""
|
|
@@ -708,8 +733,35 @@ class APIClient:
|
|
|
708
733
|
return self._get(f"/templates/{quote(name)}", params=params)
|
|
709
734
|
|
|
710
735
|
def list_benchmarks(self) -> list:
|
|
711
|
-
"""List all
|
|
712
|
-
|
|
736
|
+
"""List all benchmarks from the central registry, preserving legacy IDs."""
|
|
737
|
+
benchmarks = []
|
|
738
|
+
page = 1
|
|
739
|
+
page_size = 200
|
|
740
|
+
# 与 CLI dataset list 的自动翻页同口径:100 页上限,防分页异常导致死循环。
|
|
741
|
+
max_pages = 100
|
|
742
|
+
while page <= max_pages:
|
|
743
|
+
payload = self._get(
|
|
744
|
+
"/apis/v1/benchmarks", params={"page": page, "page_size": page_size}
|
|
745
|
+
)
|
|
746
|
+
records, pagination = _split_paged(payload)
|
|
747
|
+
for record in records:
|
|
748
|
+
item = dict(record)
|
|
749
|
+
if "source_id" in item:
|
|
750
|
+
item.setdefault("id", item["source_id"])
|
|
751
|
+
benchmarks.append(item)
|
|
752
|
+
if not records or not pagination:
|
|
753
|
+
break
|
|
754
|
+
try:
|
|
755
|
+
total = int(pagination.get("total"))
|
|
756
|
+
except (TypeError, ValueError):
|
|
757
|
+
total = None
|
|
758
|
+
if total is not None:
|
|
759
|
+
if len(benchmarks) >= total:
|
|
760
|
+
break
|
|
761
|
+
elif len(records) < page_size:
|
|
762
|
+
break
|
|
763
|
+
page += 1
|
|
764
|
+
return benchmarks
|
|
713
765
|
|
|
714
766
|
def get_benchmark(self, name: str) -> dict:
|
|
715
767
|
"""Get a single benchmark by exact name."""
|
|
@@ -844,7 +896,16 @@ class APIClient:
|
|
|
844
896
|
"instance_ids": all_instance_ids,
|
|
845
897
|
}
|
|
846
898
|
|
|
847
|
-
# ====================
|
|
899
|
+
# ==================== Dataset series (apiserver dataset domain) ====================
|
|
900
|
+
#
|
|
901
|
+
# 这一节对接 Go apiserver 的 dataset 领域(/apis/v1/datasets/...),与上面
|
|
902
|
+
# ossdata 目录的 /api/datasets 路由完全无关。
|
|
903
|
+
#
|
|
904
|
+
# **dataset 领域的控制面读写全部在 InstanceRepo SDK 上**(见
|
|
905
|
+
# docs/ap-cli-dataset-refactor-design.md §9.5):迁移期加进这里的 registry 方法、
|
|
906
|
+
# 以及最后三条"push 前检查 + publish-status"的旧读,都已随迁移删除。
|
|
907
|
+
# 本文件现在只服务旧 ossdata 路径(`dataset list --legacy`、弃用的
|
|
908
|
+
# `dataset versions/instances`、`job create --dataset`)。
|
|
848
909
|
|
|
849
910
|
def list_meta_models(self) -> dict:
|
|
850
911
|
"""模型词表(apiserver 权威源 GET /apis/v1/meta-models,返回 {items:[...]})。"""
|
|
@@ -911,9 +972,19 @@ class APIClient:
|
|
|
911
972
|
checkpoint: Optional[dict] = None,
|
|
912
973
|
benchmark: Optional[str] = None,
|
|
913
974
|
experiment: Optional[str] = None,
|
|
975
|
+
ak_ids: Optional[list[str]] = None,
|
|
914
976
|
) -> dict:
|
|
915
977
|
"""Create a group."""
|
|
978
|
+
if ak_ids is not None and not isinstance(ak_ids, list):
|
|
979
|
+
raise ValueError("ak_ids must be a list")
|
|
980
|
+
references = list(ak_ids or [])
|
|
981
|
+
for reference in references:
|
|
982
|
+
if validate_ak_id(reference) is None:
|
|
983
|
+
raise ValueError("ak_ids entries must be nonempty strings")
|
|
984
|
+
references = list(dict.fromkeys(references))
|
|
916
985
|
body: dict = {}
|
|
986
|
+
if references:
|
|
987
|
+
body["ak_ids"] = references
|
|
917
988
|
if name:
|
|
918
989
|
body["name"] = name
|
|
919
990
|
effective_tags = _merge_meta_tags(
|
|
@@ -965,11 +1036,14 @@ class APIClient:
|
|
|
965
1036
|
body["benchmark"] = benchmark
|
|
966
1037
|
if experiment is not None:
|
|
967
1038
|
body["experiment"] = experiment
|
|
968
|
-
|
|
1039
|
+
result = self._post("/groups", body)
|
|
1040
|
+
for reference in references:
|
|
1041
|
+
require_managed_ak_ack(result, reference)
|
|
1042
|
+
return result
|
|
969
1043
|
|
|
970
1044
|
def create_job(
|
|
971
1045
|
self,
|
|
972
|
-
template: str,
|
|
1046
|
+
template: Optional[str] = None,
|
|
973
1047
|
params: Optional[dict] = None,
|
|
974
1048
|
params_list: Optional[list] = None,
|
|
975
1049
|
suite_name: Optional[str] = None,
|
|
@@ -1022,6 +1096,7 @@ class APIClient:
|
|
|
1022
1096
|
checkpoint: Optional[dict] = None,
|
|
1023
1097
|
benchmark: Optional[str] = None,
|
|
1024
1098
|
experiment: Optional[str] = None,
|
|
1099
|
+
ak_id: Optional[str] = None,
|
|
1025
1100
|
) -> dict:
|
|
1026
1101
|
"""Create a job."""
|
|
1027
1102
|
body = self.build_create_job_body(
|
|
@@ -1068,13 +1143,16 @@ class APIClient:
|
|
|
1068
1143
|
checkpoint=checkpoint,
|
|
1069
1144
|
benchmark=benchmark,
|
|
1070
1145
|
experiment=experiment,
|
|
1146
|
+
ak_id=ak_id,
|
|
1071
1147
|
)
|
|
1072
1148
|
|
|
1073
|
-
|
|
1149
|
+
result = self._post("/jobs", body, timeout=timeout)
|
|
1150
|
+
require_managed_ak_ack(result, ak_id)
|
|
1151
|
+
return result
|
|
1074
1152
|
|
|
1075
1153
|
def build_create_job_body(
|
|
1076
1154
|
self,
|
|
1077
|
-
template: str,
|
|
1155
|
+
template: Optional[str] = None,
|
|
1078
1156
|
params: Optional[dict] = None,
|
|
1079
1157
|
params_list: Optional[list] = None,
|
|
1080
1158
|
suite_name: Optional[str] = None,
|
|
@@ -1126,6 +1204,7 @@ class APIClient:
|
|
|
1126
1204
|
checkpoint: Optional[dict] = None,
|
|
1127
1205
|
benchmark: Optional[str] = None,
|
|
1128
1206
|
experiment: Optional[str] = None,
|
|
1207
|
+
ak_id: Optional[str] = None,
|
|
1129
1208
|
) -> dict:
|
|
1130
1209
|
"""Build the /jobs request body for job creation."""
|
|
1131
1210
|
_validate_credential_source_selection(
|
|
@@ -1134,7 +1213,14 @@ class APIClient:
|
|
|
1134
1213
|
params=params,
|
|
1135
1214
|
params_list=params_list,
|
|
1136
1215
|
)
|
|
1137
|
-
|
|
1216
|
+
if not template and not profile_id:
|
|
1217
|
+
raise ValueError("Either template or profile_id is required")
|
|
1218
|
+
validate_ak_selection(ak_id, params, params_list, overrides)
|
|
1219
|
+
body: dict = {}
|
|
1220
|
+
if template is not None:
|
|
1221
|
+
body["template"] = template
|
|
1222
|
+
if ak_id is not None:
|
|
1223
|
+
body["ak_id"] = ak_id
|
|
1138
1224
|
|
|
1139
1225
|
if params is not None:
|
|
1140
1226
|
body["params"] = params
|
|
@@ -1289,9 +1375,13 @@ class APIClient:
|
|
|
1289
1375
|
next_token: Optional[str] = None,
|
|
1290
1376
|
pagination: Optional[str] = None,
|
|
1291
1377
|
include_total: bool = True,
|
|
1378
|
+
ak_id: Optional[str] = None,
|
|
1292
1379
|
) -> dict:
|
|
1293
1380
|
"""List jobs."""
|
|
1381
|
+
validate_ak_id(ak_id)
|
|
1294
1382
|
params = {"skip": skip, "limit": limit}
|
|
1383
|
+
if ak_id is not None:
|
|
1384
|
+
params["ak_id"] = ak_id
|
|
1295
1385
|
if template:
|
|
1296
1386
|
params["template"] = template
|
|
1297
1387
|
if group_id:
|
|
@@ -1485,9 +1575,13 @@ class APIClient:
|
|
|
1485
1575
|
meta_job_type: Optional[str] = None,
|
|
1486
1576
|
upstream_platform: Optional[str] = None,
|
|
1487
1577
|
upstream_job_id: Optional[str] = None,
|
|
1578
|
+
ak_id: Optional[str] = None,
|
|
1488
1579
|
) -> dict:
|
|
1489
1580
|
"""List groups."""
|
|
1581
|
+
validate_ak_id(ak_id)
|
|
1490
1582
|
params: dict = {"skip": skip, "limit": limit}
|
|
1583
|
+
if ak_id is not None:
|
|
1584
|
+
params["ak_id"] = ak_id
|
|
1491
1585
|
if name is not None:
|
|
1492
1586
|
params["name"] = name
|
|
1493
1587
|
if group_id is not None:
|