ap-client 0.6.0.dev1__tar.gz → 0.7.0.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/PKG-INFO +4 -4
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/api.py +109 -5
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/cli.py +210 -222
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/exporter.py +33 -2
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/managed_ak.py +60 -19
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/profile_commands.py +109 -1
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/pyproject.toml +4 -4
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/__init__.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/akpool_commands.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/config.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/dataset_commands.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/dataset_register.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/fs_commands.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/instance_commands.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/irepo_sdk.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/manifest_view.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/repo_read_guard.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/share_api.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/split_publish.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/tbb.py +0 -0
- {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/waiter.py +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0.dev0
|
|
4
4
|
Summary: Agent Platform API Client & CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
-
Requires-Dist: irepo-artifact==0.2.
|
|
6
|
+
Requires-Dist: irepo-artifact==0.2.2
|
|
7
7
|
Requires-Dist: pyyaml>=6.0
|
|
8
8
|
Requires-Dist: requests>=2.28.0
|
|
9
9
|
Requires-Dist: rich>=13.0.0
|
|
10
10
|
Requires-Dist: typer>=0.9.0
|
|
11
11
|
Requires-Dist: websockets>=13.0
|
|
12
12
|
Provides-Extra: all
|
|
13
|
-
Requires-Dist: instance-repo[oss]<2.0.0,>=1.3.
|
|
13
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.3.5; extra == 'all'
|
|
14
14
|
Provides-Extra: dataset
|
|
15
|
-
Requires-Dist: instance-repo[oss]<2.0.0,>=1.3.
|
|
15
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.3.5; extra == 'dataset'
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|
A lightweight Python SDK and command line interface for Agent Platform. It provides helpers for configuring API access and managing templates, datasets, jobs, and groups.
|
|
@@ -1125,8 +1125,21 @@ class APIClient:
|
|
|
1125
1125
|
benchmark: Optional[str] = None,
|
|
1126
1126
|
experiment: Optional[str] = None,
|
|
1127
1127
|
ak_ids: Optional[list[str]] = None,
|
|
1128
|
+
priority: Optional[str] = None,
|
|
1129
|
+
priority_weight: Optional[int] = None,
|
|
1130
|
+
transparency: Optional[Literal["whitebox", "blackbox"]] = None,
|
|
1131
|
+
idempotency_key: Optional[str] = None,
|
|
1128
1132
|
) -> dict:
|
|
1129
|
-
"""Create a group.
|
|
1133
|
+
"""Create a group with optional scheduling defaults for its Jobs.
|
|
1134
|
+
|
|
1135
|
+
Omitted priority fields use the server defaults: medium and weight 0.
|
|
1136
|
+
"""
|
|
1137
|
+
if priority is not None and priority not in {"high", "medium"}:
|
|
1138
|
+
raise ValueError("priority must be one of high/medium")
|
|
1139
|
+
if transparency is not None and transparency not in {"whitebox", "blackbox"}:
|
|
1140
|
+
raise ValueError("transparency must be whitebox or blackbox")
|
|
1141
|
+
if transparency == "blackbox" and not idempotency_key:
|
|
1142
|
+
raise ValueError("blackbox Group creation requires a reusable idempotency_key (UUID4)")
|
|
1130
1143
|
if ak_ids is not None and not isinstance(ak_ids, list):
|
|
1131
1144
|
raise ValueError("ak_ids must be a list")
|
|
1132
1145
|
references = list(ak_ids or [])
|
|
@@ -1135,6 +1148,10 @@ class APIClient:
|
|
|
1135
1148
|
raise ValueError("ak_ids entries must be nonempty strings")
|
|
1136
1149
|
references = list(dict.fromkeys(references))
|
|
1137
1150
|
body: dict = {}
|
|
1151
|
+
if transparency is not None:
|
|
1152
|
+
body["transparency"] = transparency
|
|
1153
|
+
if idempotency_key is not None:
|
|
1154
|
+
body["idempotency_key"] = idempotency_key
|
|
1138
1155
|
if references:
|
|
1139
1156
|
body["ak_ids"] = references
|
|
1140
1157
|
if name:
|
|
@@ -1164,6 +1181,10 @@ class APIClient:
|
|
|
1164
1181
|
body["queue_id"] = effective_queue_id
|
|
1165
1182
|
if max_concurrency is not None:
|
|
1166
1183
|
body["max_concurrency"] = max_concurrency
|
|
1184
|
+
if priority is not None:
|
|
1185
|
+
body["priority"] = priority
|
|
1186
|
+
if priority_weight is not None:
|
|
1187
|
+
body["priority_weight"] = priority_weight
|
|
1167
1188
|
if eval_config is not None:
|
|
1168
1189
|
body["eval_config"] = eval_config
|
|
1169
1190
|
if model_base_url_collection is not None:
|
|
@@ -1252,9 +1273,11 @@ class APIClient:
|
|
|
1252
1273
|
ak_mode: str = "auto",
|
|
1253
1274
|
instance_range: Optional[str] = None,
|
|
1254
1275
|
profile: Optional[str] = None,
|
|
1276
|
+
transparency: Optional[Literal["whitebox", "blackbox"]] = None,
|
|
1255
1277
|
) -> dict:
|
|
1256
1278
|
"""Create a job."""
|
|
1257
1279
|
body = self.build_create_job_body(
|
|
1280
|
+
transparency=transparency,
|
|
1258
1281
|
template=template,
|
|
1259
1282
|
params=params,
|
|
1260
1283
|
params_list=params_list,
|
|
@@ -1366,8 +1389,11 @@ class APIClient:
|
|
|
1366
1389
|
ak_mode: str = "auto",
|
|
1367
1390
|
instance_range: Optional[str] = None,
|
|
1368
1391
|
profile: Optional[str] = None,
|
|
1392
|
+
transparency: Optional[Literal["whitebox", "blackbox"]] = None,
|
|
1369
1393
|
) -> dict:
|
|
1370
1394
|
"""Build the /jobs request body for job creation."""
|
|
1395
|
+
if transparency is not None and transparency not in {"whitebox", "blackbox"}:
|
|
1396
|
+
raise ValueError("transparency must be whitebox or blackbox")
|
|
1371
1397
|
if profile is not None and (profile_id is not None or profile_version is not None):
|
|
1372
1398
|
raise ValueError("profile cannot be combined with profile_id or profile_version")
|
|
1373
1399
|
_validate_credential_source_selection(
|
|
@@ -1492,8 +1518,23 @@ class APIClient:
|
|
|
1492
1518
|
if experiment is not None:
|
|
1493
1519
|
body["experiment"] = experiment
|
|
1494
1520
|
|
|
1521
|
+
if transparency is not None:
|
|
1522
|
+
body["transparency"] = transparency
|
|
1495
1523
|
return body
|
|
1496
1524
|
|
|
1525
|
+
def get_job_access_contact(self, job_id: str) -> dict:
|
|
1526
|
+
"""Discover the owner and grant contact without reading protected details."""
|
|
1527
|
+
result = self._get(f"/jobs/{quote(job_id, safe='')}", params={"access_contact": True})
|
|
1528
|
+
if (
|
|
1529
|
+
not isinstance(result, dict)
|
|
1530
|
+
or result.get("resource_type") != "job"
|
|
1531
|
+
or "authorization_contact" not in result
|
|
1532
|
+
):
|
|
1533
|
+
raise ConfigurationError(
|
|
1534
|
+
"Server does not support access contact queries; upgrade Agent-Platform first"
|
|
1535
|
+
)
|
|
1536
|
+
return result
|
|
1537
|
+
|
|
1497
1538
|
def get_job(
|
|
1498
1539
|
self,
|
|
1499
1540
|
job_id: str,
|
|
@@ -1619,6 +1660,19 @@ class APIClient:
|
|
|
1619
1660
|
|
|
1620
1661
|
# ==================== Group operations ====================
|
|
1621
1662
|
|
|
1663
|
+
def get_group_access_contact(self, group_id: str) -> dict:
|
|
1664
|
+
"""Discover the owner and grant contact without reading protected details."""
|
|
1665
|
+
result = self._get(f"/groups/{quote(group_id, safe='')}", params={"access_contact": True})
|
|
1666
|
+
if (
|
|
1667
|
+
not isinstance(result, dict)
|
|
1668
|
+
or result.get("resource_type") != "group"
|
|
1669
|
+
or "authorization_contact" not in result
|
|
1670
|
+
):
|
|
1671
|
+
raise ConfigurationError(
|
|
1672
|
+
"Server does not support access contact queries; upgrade Agent-Platform first"
|
|
1673
|
+
)
|
|
1674
|
+
return result
|
|
1675
|
+
|
|
1622
1676
|
def get_group(self, group_id: str, timeout: TimeoutType = None) -> dict:
|
|
1623
1677
|
"""Get group details."""
|
|
1624
1678
|
return self._get(f"/groups/{quote(group_id)}", timeout=timeout)
|
|
@@ -1881,9 +1935,15 @@ class APIClient:
|
|
|
1881
1935
|
meta_job_type: Optional[str] = None,
|
|
1882
1936
|
meta_teacher_direction: Optional[str] = None,
|
|
1883
1937
|
meta_train_stage: Optional[str] = None,
|
|
1938
|
+
benchmark: Optional[str] = None,
|
|
1939
|
+
priority: Optional[str] = None,
|
|
1940
|
+
priority_weight: Optional[int] = None,
|
|
1941
|
+
priority_update_mode: Optional[str] = None,
|
|
1884
1942
|
) -> dict:
|
|
1885
1943
|
"""Update group properties.
|
|
1886
1944
|
|
|
1945
|
+
``benchmark`` is omitted when None; pass an empty string to clear it.
|
|
1946
|
+
|
|
1887
1947
|
``tags``(`key:value` 数组)在客户端先与 Group 现有 tags 合并,再以
|
|
1888
1948
|
全量替换语义提交(服务端 PATCH 是整体替换);同名 key 覆盖,未提及
|
|
1889
1949
|
的 key 保留。服务端会从 tags 同步 meta_* 等派生字段。
|
|
@@ -1891,7 +1951,19 @@ class APIClient:
|
|
|
1891
1951
|
``meta_*`` 独立属性为部分更新(传了才改,不支持清除);新服务端
|
|
1892
1952
|
优先读属性,老服务端忽略属性——因此过渡期内同时也会将属性合并进
|
|
1893
1953
|
tags 轨(双写)。
|
|
1954
|
+
|
|
1955
|
+
Priority changes update the Group defaults. The default existing mode
|
|
1956
|
+
also updates all current Jobs, including PostProcess; incremental mode
|
|
1957
|
+
changes only the defaults for future Jobs. Updating priority alone uses
|
|
1958
|
+
weight 0; updating only weight preserves the current priority tier.
|
|
1894
1959
|
"""
|
|
1960
|
+
if priority is not None and priority not in {"high", "medium"}:
|
|
1961
|
+
raise ValueError("priority must be one of high/medium")
|
|
1962
|
+
if priority_update_mode is not None:
|
|
1963
|
+
if priority_update_mode not in {"existing", "incremental"}:
|
|
1964
|
+
raise ValueError("priority_update_mode must be one of existing/incremental")
|
|
1965
|
+
if priority is None and priority_weight is None:
|
|
1966
|
+
raise ValueError("priority_update_mode requires priority or priority_weight")
|
|
1895
1967
|
body: dict = {}
|
|
1896
1968
|
|
|
1897
1969
|
if max_concurrency is not None:
|
|
@@ -1906,6 +1978,14 @@ class APIClient:
|
|
|
1906
1978
|
body["model"] = model
|
|
1907
1979
|
if scaffold is not None:
|
|
1908
1980
|
body["scaffold"] = scaffold
|
|
1981
|
+
if benchmark is not None:
|
|
1982
|
+
body["benchmark"] = benchmark
|
|
1983
|
+
if priority is not None:
|
|
1984
|
+
body["priority"] = priority
|
|
1985
|
+
if priority_weight is not None:
|
|
1986
|
+
body["priority_weight"] = priority_weight
|
|
1987
|
+
if priority_update_mode is not None:
|
|
1988
|
+
body["priority_update_mode"] = priority_update_mode
|
|
1909
1989
|
meta_attr_tags = _merge_meta_tags(
|
|
1910
1990
|
None,
|
|
1911
1991
|
meta_model=meta_model,
|
|
@@ -3009,6 +3089,23 @@ class APIClient:
|
|
|
3009
3089
|
return self._get(f"{self._PROFILES_BASE}/{quote(id_or_name)}/versions/{version}")
|
|
3010
3090
|
return self._get(f"{self._PROFILES_BASE}/{quote(id_or_name)}")
|
|
3011
3091
|
|
|
3092
|
+
def update_profile_job_meta(self, profile_id: str, patch: dict) -> dict:
|
|
3093
|
+
"""Patch live Job defaults without changing the Profile version.
|
|
3094
|
+
|
|
3095
|
+
Omitted fields stay unchanged. Set benchmark to None to clear it.
|
|
3096
|
+
Tags use {"append": [...], "remove": [...]} and are applied atomically.
|
|
3097
|
+
"""
|
|
3098
|
+
return self._request_central(
|
|
3099
|
+
"PATCH", f"/profiles/{quote(profile_id, safe='')}/job-meta", json_body=patch
|
|
3100
|
+
)
|
|
3101
|
+
|
|
3102
|
+
def replace_profile_job_meta(self, profile_id: str, job_meta: dict) -> dict:
|
|
3103
|
+
"""Replace all live Job defaults; {} clears benchmark and tags.
|
|
3104
|
+
|
|
3105
|
+
This does not change spec, Profile tags, version, verification or release.
|
|
3106
|
+
"""
|
|
3107
|
+
return self._put_central(f"/profiles/{quote(profile_id, safe='')}/job-meta", job_meta)
|
|
3108
|
+
|
|
3012
3109
|
def update_profile(
|
|
3013
3110
|
self,
|
|
3014
3111
|
profile_id: str,
|
|
@@ -3017,8 +3114,13 @@ class APIClient:
|
|
|
3017
3114
|
description: str = "",
|
|
3018
3115
|
account_pool: Optional[str] = None,
|
|
3019
3116
|
metadata: Optional[dict] = None,
|
|
3117
|
+
reason: Optional[str] = None,
|
|
3020
3118
|
) -> dict:
|
|
3021
|
-
"""Update a submission profile, preserving an omitted default Pool.
|
|
3119
|
+
"""Update a submission profile, preserving an omitted default Pool.
|
|
3120
|
+
|
|
3121
|
+
reason describes this update; omission never reuses a previous reason.
|
|
3122
|
+
The server validates whether it is required and its 500-character limit.
|
|
3123
|
+
"""
|
|
3022
3124
|
body: dict = {"name": name, "spec": spec}
|
|
3023
3125
|
if description:
|
|
3024
3126
|
body["description"] = description
|
|
@@ -3026,6 +3128,8 @@ class APIClient:
|
|
|
3026
3128
|
body["account_pool"] = account_pool
|
|
3027
3129
|
if metadata is not None:
|
|
3028
3130
|
body["metadata"] = metadata
|
|
3131
|
+
if reason is not None:
|
|
3132
|
+
body["reason"] = reason
|
|
3029
3133
|
return self._put(f"{self._PROFILES_BASE}/{quote(profile_id)}", body)
|
|
3030
3134
|
|
|
3031
3135
|
def delete_profile(self, profile_id: str) -> dict:
|
|
@@ -3048,8 +3152,8 @@ class APIClient:
|
|
|
3048
3152
|
) -> dict:
|
|
3049
3153
|
"""把某个 job/group 实例的访问权授予某 user/user_group(scope=resource 绑定)。
|
|
3050
3154
|
|
|
3051
|
-
workspace_id = 该实例所属 queue_id;
|
|
3052
|
-
|
|
3155
|
+
workspace_id = 该实例所属 queue_id;Job/Group Owner、Group Admin 或所属
|
|
3156
|
+
WorkSpace 的管理员/创建者及平台管理员可授权。鉴权走当前 API Key 解析出的调用者身份。
|
|
3053
3157
|
"""
|
|
3054
3158
|
body = {
|
|
3055
3159
|
"scope": "resource",
|
|
@@ -3315,7 +3419,7 @@ def _response_error_detail(resp: requests.Response) -> Any:
|
|
|
3315
3419
|
except Exception:
|
|
3316
3420
|
return resp.text
|
|
3317
3421
|
if isinstance(err_data, dict):
|
|
3318
|
-
return err_data.get("detail", resp.text)
|
|
3422
|
+
return err_data.get("detail", err_data.get("message", resp.text))
|
|
3319
3423
|
return err_data
|
|
3320
3424
|
|
|
3321
3425
|
|