ap-client 0.3.0.dev0__tar.gz → 0.3.1.dev1__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.dev1}/PKG-INFO +3 -3
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev1}/ap_client/api.py +121 -9
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev1}/ap_client/cli.py +181 -53
- ap_client-0.3.1.dev1/ap_client/dataset_commands.py +1905 -0
- ap_client-0.3.1.dev1/ap_client/fs_commands.py +676 -0
- ap_client-0.3.1.dev1/ap_client/instance_commands.py +718 -0
- ap_client-0.3.1.dev1/ap_client/irepo_sdk.py +365 -0
- ap_client-0.3.1.dev1/ap_client/managed_ak.py +86 -0
- ap_client-0.3.1.dev1/ap_client/split_publish.py +133 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev1}/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.dev1}/ap_client/__init__.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev1}/ap_client/config.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev1}/ap_client/exporter.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev1}/ap_client/profile_commands.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev1}/ap_client/tbb.py +0 -0
- {ap_client-0.3.0.dev0 → ap_client-0.3.1.dev1}/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.dev1
|
|
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,43 @@ 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 _unwrap_data(payload: Any) -> Any:
|
|
298
|
+
"""Unwrap the apiserver ``{code,message,data}`` envelope when present.
|
|
299
|
+
|
|
300
|
+
The dataset-domain detail/create/patch endpoints return a bare JSON body,
|
|
301
|
+
but the same handlers are occasionally wrapped by the generic success
|
|
302
|
+
envelope. Tolerate both so command code never has to branch: a mapping that
|
|
303
|
+
carries ``data`` alongside ``code``/``message`` is treated as an envelope,
|
|
304
|
+
anything else is returned verbatim.
|
|
305
|
+
"""
|
|
306
|
+
if (
|
|
307
|
+
isinstance(payload, dict)
|
|
308
|
+
and "data" in payload
|
|
309
|
+
and ("code" in payload or "message" in payload)
|
|
310
|
+
):
|
|
311
|
+
return payload["data"]
|
|
312
|
+
return payload
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _split_paged(payload: Any) -> tuple[list[dict], dict]:
|
|
316
|
+
"""Split a ``PagedSuccess`` envelope into ``(records, pagination)``.
|
|
317
|
+
|
|
318
|
+
``PagedSuccess`` is ``{code,message,data:[...],pagination:{total,page,page_size}}``.
|
|
319
|
+
A bare list (or a null ``data``) yields an empty pagination dict so callers
|
|
320
|
+
can render results without probing the response shape.
|
|
321
|
+
"""
|
|
322
|
+
if isinstance(payload, list):
|
|
323
|
+
return [item for item in payload if isinstance(item, dict)], {}
|
|
324
|
+
if not isinstance(payload, dict):
|
|
325
|
+
return [], {}
|
|
326
|
+
records = payload.get("data")
|
|
327
|
+
pagination = payload.get("pagination")
|
|
328
|
+
return (
|
|
329
|
+
[item for item in (records or []) if isinstance(item, dict)],
|
|
330
|
+
pagination if isinstance(pagination, dict) else {},
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
296
334
|
class APIClient:
|
|
297
335
|
"""Agent Platform API client."""
|
|
298
336
|
|
|
@@ -459,7 +497,12 @@ class APIClient:
|
|
|
459
497
|
|
|
460
498
|
def _post(self, path: str, data: Any, timeout: TimeoutType = None) -> Any:
|
|
461
499
|
"""Send a POST request."""
|
|
462
|
-
|
|
500
|
+
from .managed_ak import parameter_ak_ids, require_parameter_ak_ack
|
|
501
|
+
|
|
502
|
+
references = parameter_ak_ids(data) if path == "/jobs" else set()
|
|
503
|
+
result = self._request("POST", path, json_body=data, timeout=timeout)
|
|
504
|
+
require_parameter_ak_ack(result, references)
|
|
505
|
+
return result
|
|
463
506
|
|
|
464
507
|
def _delete(self, path: str, *, params: Optional[dict] = None) -> Any:
|
|
465
508
|
"""Send a DELETE request."""
|
|
@@ -708,8 +751,35 @@ class APIClient:
|
|
|
708
751
|
return self._get(f"/templates/{quote(name)}", params=params)
|
|
709
752
|
|
|
710
753
|
def list_benchmarks(self) -> list:
|
|
711
|
-
"""List all
|
|
712
|
-
|
|
754
|
+
"""List all benchmarks from the central registry, preserving legacy IDs."""
|
|
755
|
+
benchmarks = []
|
|
756
|
+
page = 1
|
|
757
|
+
page_size = 200
|
|
758
|
+
# 与 CLI dataset list 的自动翻页同口径:100 页上限,防分页异常导致死循环。
|
|
759
|
+
max_pages = 100
|
|
760
|
+
while page <= max_pages:
|
|
761
|
+
payload = self._get(
|
|
762
|
+
"/apis/v1/benchmarks", params={"page": page, "page_size": page_size}
|
|
763
|
+
)
|
|
764
|
+
records, pagination = _split_paged(payload)
|
|
765
|
+
for record in records:
|
|
766
|
+
item = dict(record)
|
|
767
|
+
if "source_id" in item:
|
|
768
|
+
item.setdefault("id", item["source_id"])
|
|
769
|
+
benchmarks.append(item)
|
|
770
|
+
if not records or not pagination:
|
|
771
|
+
break
|
|
772
|
+
try:
|
|
773
|
+
total = int(pagination.get("total"))
|
|
774
|
+
except (TypeError, ValueError):
|
|
775
|
+
total = None
|
|
776
|
+
if total is not None:
|
|
777
|
+
if len(benchmarks) >= total:
|
|
778
|
+
break
|
|
779
|
+
elif len(records) < page_size:
|
|
780
|
+
break
|
|
781
|
+
page += 1
|
|
782
|
+
return benchmarks
|
|
713
783
|
|
|
714
784
|
def get_benchmark(self, name: str) -> dict:
|
|
715
785
|
"""Get a single benchmark by exact name."""
|
|
@@ -844,7 +914,16 @@ class APIClient:
|
|
|
844
914
|
"instance_ids": all_instance_ids,
|
|
845
915
|
}
|
|
846
916
|
|
|
847
|
-
# ====================
|
|
917
|
+
# ==================== Dataset series (apiserver dataset domain) ====================
|
|
918
|
+
#
|
|
919
|
+
# 这一节对接 Go apiserver 的 dataset 领域(/apis/v1/datasets/...),与上面
|
|
920
|
+
# ossdata 目录的 /api/datasets 路由完全无关。
|
|
921
|
+
#
|
|
922
|
+
# **dataset 领域的控制面读写全部在 InstanceRepo SDK 上**(见
|
|
923
|
+
# docs/ap-cli-dataset-refactor-design.md §9.5):迁移期加进这里的 registry 方法、
|
|
924
|
+
# 以及最后三条"push 前检查 + publish-status"的旧读,都已随迁移删除。
|
|
925
|
+
# 本文件现在只服务旧 ossdata 路径(`dataset list --legacy`、弃用的
|
|
926
|
+
# `dataset versions/instances`、`job create --dataset`)。
|
|
848
927
|
|
|
849
928
|
def list_meta_models(self) -> dict:
|
|
850
929
|
"""模型词表(apiserver 权威源 GET /apis/v1/meta-models,返回 {items:[...]})。"""
|
|
@@ -911,9 +990,19 @@ class APIClient:
|
|
|
911
990
|
checkpoint: Optional[dict] = None,
|
|
912
991
|
benchmark: Optional[str] = None,
|
|
913
992
|
experiment: Optional[str] = None,
|
|
993
|
+
ak_ids: Optional[list[str]] = None,
|
|
914
994
|
) -> dict:
|
|
915
995
|
"""Create a group."""
|
|
996
|
+
if ak_ids is not None and not isinstance(ak_ids, list):
|
|
997
|
+
raise ValueError("ak_ids must be a list")
|
|
998
|
+
references = list(ak_ids or [])
|
|
999
|
+
for reference in references:
|
|
1000
|
+
if validate_ak_id(reference) is None:
|
|
1001
|
+
raise ValueError("ak_ids entries must be nonempty strings")
|
|
1002
|
+
references = list(dict.fromkeys(references))
|
|
916
1003
|
body: dict = {}
|
|
1004
|
+
if references:
|
|
1005
|
+
body["ak_ids"] = references
|
|
917
1006
|
if name:
|
|
918
1007
|
body["name"] = name
|
|
919
1008
|
effective_tags = _merge_meta_tags(
|
|
@@ -965,11 +1054,14 @@ class APIClient:
|
|
|
965
1054
|
body["benchmark"] = benchmark
|
|
966
1055
|
if experiment is not None:
|
|
967
1056
|
body["experiment"] = experiment
|
|
968
|
-
|
|
1057
|
+
result = self._post("/groups", body)
|
|
1058
|
+
for reference in references:
|
|
1059
|
+
require_managed_ak_ack(result, reference)
|
|
1060
|
+
return result
|
|
969
1061
|
|
|
970
1062
|
def create_job(
|
|
971
1063
|
self,
|
|
972
|
-
template: str,
|
|
1064
|
+
template: Optional[str] = None,
|
|
973
1065
|
params: Optional[dict] = None,
|
|
974
1066
|
params_list: Optional[list] = None,
|
|
975
1067
|
suite_name: Optional[str] = None,
|
|
@@ -1022,6 +1114,7 @@ class APIClient:
|
|
|
1022
1114
|
checkpoint: Optional[dict] = None,
|
|
1023
1115
|
benchmark: Optional[str] = None,
|
|
1024
1116
|
experiment: Optional[str] = None,
|
|
1117
|
+
ak_id: Optional[str] = None,
|
|
1025
1118
|
) -> dict:
|
|
1026
1119
|
"""Create a job."""
|
|
1027
1120
|
body = self.build_create_job_body(
|
|
@@ -1068,13 +1161,16 @@ class APIClient:
|
|
|
1068
1161
|
checkpoint=checkpoint,
|
|
1069
1162
|
benchmark=benchmark,
|
|
1070
1163
|
experiment=experiment,
|
|
1164
|
+
ak_id=ak_id,
|
|
1071
1165
|
)
|
|
1072
1166
|
|
|
1073
|
-
|
|
1167
|
+
result = self._post("/jobs", body, timeout=timeout)
|
|
1168
|
+
require_managed_ak_ack(result, ak_id)
|
|
1169
|
+
return result
|
|
1074
1170
|
|
|
1075
1171
|
def build_create_job_body(
|
|
1076
1172
|
self,
|
|
1077
|
-
template: str,
|
|
1173
|
+
template: Optional[str] = None,
|
|
1078
1174
|
params: Optional[dict] = None,
|
|
1079
1175
|
params_list: Optional[list] = None,
|
|
1080
1176
|
suite_name: Optional[str] = None,
|
|
@@ -1126,6 +1222,7 @@ class APIClient:
|
|
|
1126
1222
|
checkpoint: Optional[dict] = None,
|
|
1127
1223
|
benchmark: Optional[str] = None,
|
|
1128
1224
|
experiment: Optional[str] = None,
|
|
1225
|
+
ak_id: Optional[str] = None,
|
|
1129
1226
|
) -> dict:
|
|
1130
1227
|
"""Build the /jobs request body for job creation."""
|
|
1131
1228
|
_validate_credential_source_selection(
|
|
@@ -1134,7 +1231,14 @@ class APIClient:
|
|
|
1134
1231
|
params=params,
|
|
1135
1232
|
params_list=params_list,
|
|
1136
1233
|
)
|
|
1137
|
-
|
|
1234
|
+
if not template and not profile_id:
|
|
1235
|
+
raise ValueError("Either template or profile_id is required")
|
|
1236
|
+
validate_ak_selection(ak_id, params, params_list, overrides)
|
|
1237
|
+
body: dict = {}
|
|
1238
|
+
if template is not None:
|
|
1239
|
+
body["template"] = template
|
|
1240
|
+
if ak_id is not None:
|
|
1241
|
+
body["ak_id"] = ak_id
|
|
1138
1242
|
|
|
1139
1243
|
if params is not None:
|
|
1140
1244
|
body["params"] = params
|
|
@@ -1289,9 +1393,13 @@ class APIClient:
|
|
|
1289
1393
|
next_token: Optional[str] = None,
|
|
1290
1394
|
pagination: Optional[str] = None,
|
|
1291
1395
|
include_total: bool = True,
|
|
1396
|
+
ak_id: Optional[str] = None,
|
|
1292
1397
|
) -> dict:
|
|
1293
1398
|
"""List jobs."""
|
|
1399
|
+
validate_ak_id(ak_id)
|
|
1294
1400
|
params = {"skip": skip, "limit": limit}
|
|
1401
|
+
if ak_id is not None:
|
|
1402
|
+
params["ak_id"] = ak_id
|
|
1295
1403
|
if template:
|
|
1296
1404
|
params["template"] = template
|
|
1297
1405
|
if group_id:
|
|
@@ -1485,9 +1593,13 @@ class APIClient:
|
|
|
1485
1593
|
meta_job_type: Optional[str] = None,
|
|
1486
1594
|
upstream_platform: Optional[str] = None,
|
|
1487
1595
|
upstream_job_id: Optional[str] = None,
|
|
1596
|
+
ak_id: Optional[str] = None,
|
|
1488
1597
|
) -> dict:
|
|
1489
1598
|
"""List groups."""
|
|
1599
|
+
validate_ak_id(ak_id)
|
|
1490
1600
|
params: dict = {"skip": skip, "limit": limit}
|
|
1601
|
+
if ak_id is not None:
|
|
1602
|
+
params["ak_id"] = ak_id
|
|
1491
1603
|
if name is not None:
|
|
1492
1604
|
params["name"] = name
|
|
1493
1605
|
if group_id is not None:
|