ap-client 0.6.1.dev0__tar.gz → 0.6.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.6.1.dev0 → ap_client-0.6.1.dev2}/PKG-INFO +4 -6
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/api.py +104 -5
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/cli.py +204 -222
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/dataset_commands.py +90 -23
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/dfs_commands.py +4 -4
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/exporter.py +33 -2
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/instance_commands.py +1 -1
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/irepo_sdk.py +2 -4
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/managed_ak.py +60 -19
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/ossutil_io.py +63 -1
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/profile_commands.py +128 -16
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/pyproject.toml +4 -6
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/__init__.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/akpool_commands.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/config.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/dataset_name.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/dataset_register.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/fs_commands.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/instance_options.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/manifest_view.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/ossutil_hash.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/repo_read_guard.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/share_api.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/split_publish.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/tbb.py +0 -0
- {ap_client-0.6.1.dev0 → ap_client-0.6.1.dev2}/ap_client/waiter.py +0 -0
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.6.1.
|
|
3
|
+
Version: 0.6.1.dev2
|
|
4
4
|
Summary: Agent Platform API Client & CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
-
Requires-Dist:
|
|
7
|
-
Requires-Dist: irepo-artifact==0.2.1
|
|
6
|
+
Requires-Dist: irepo-artifact==0.2.2
|
|
8
7
|
Requires-Dist: pyyaml>=6.0
|
|
9
8
|
Requires-Dist: requests>=2.28.0
|
|
10
9
|
Requires-Dist: rich>=13.0.0
|
|
11
10
|
Requires-Dist: typer>=0.9.0
|
|
12
|
-
Requires-Dist: urllib3>=1.26
|
|
13
11
|
Requires-Dist: websockets>=13.0
|
|
14
12
|
Provides-Extra: all
|
|
15
|
-
Requires-Dist: instance-repo[oss]<2.0.0,>=1.4.0.
|
|
13
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.4.0.dev4; extra == 'all'
|
|
16
14
|
Provides-Extra: dataset
|
|
17
|
-
Requires-Dist: instance-repo[oss]<2.0.0,>=1.4.0.
|
|
15
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.4.0.dev4; extra == 'dataset'
|
|
18
16
|
Description-Content-Type: text/markdown
|
|
19
17
|
|
|
20
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)
|
|
@@ -1882,6 +1936,9 @@ class APIClient:
|
|
|
1882
1936
|
meta_teacher_direction: Optional[str] = None,
|
|
1883
1937
|
meta_train_stage: Optional[str] = None,
|
|
1884
1938
|
benchmark: Optional[str] = None,
|
|
1939
|
+
priority: Optional[str] = None,
|
|
1940
|
+
priority_weight: Optional[int] = None,
|
|
1941
|
+
priority_update_mode: Optional[str] = None,
|
|
1885
1942
|
) -> dict:
|
|
1886
1943
|
"""Update group properties.
|
|
1887
1944
|
|
|
@@ -1894,7 +1951,19 @@ class APIClient:
|
|
|
1894
1951
|
``meta_*`` 独立属性为部分更新(传了才改,不支持清除);新服务端
|
|
1895
1952
|
优先读属性,老服务端忽略属性——因此过渡期内同时也会将属性合并进
|
|
1896
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.
|
|
1897
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")
|
|
1898
1967
|
body: dict = {}
|
|
1899
1968
|
|
|
1900
1969
|
if max_concurrency is not None:
|
|
@@ -1911,6 +1980,12 @@ class APIClient:
|
|
|
1911
1980
|
body["scaffold"] = scaffold
|
|
1912
1981
|
if benchmark is not None:
|
|
1913
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
|
|
1914
1989
|
meta_attr_tags = _merge_meta_tags(
|
|
1915
1990
|
None,
|
|
1916
1991
|
meta_model=meta_model,
|
|
@@ -3014,6 +3089,23 @@ class APIClient:
|
|
|
3014
3089
|
return self._get(f"{self._PROFILES_BASE}/{quote(id_or_name)}/versions/{version}")
|
|
3015
3090
|
return self._get(f"{self._PROFILES_BASE}/{quote(id_or_name)}")
|
|
3016
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
|
+
|
|
3017
3109
|
def update_profile(
|
|
3018
3110
|
self,
|
|
3019
3111
|
profile_id: str,
|
|
@@ -3022,8 +3114,13 @@ class APIClient:
|
|
|
3022
3114
|
description: str = "",
|
|
3023
3115
|
account_pool: Optional[str] = None,
|
|
3024
3116
|
metadata: Optional[dict] = None,
|
|
3117
|
+
reason: Optional[str] = None,
|
|
3025
3118
|
) -> dict:
|
|
3026
|
-
"""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
|
+
"""
|
|
3027
3124
|
body: dict = {"name": name, "spec": spec}
|
|
3028
3125
|
if description:
|
|
3029
3126
|
body["description"] = description
|
|
@@ -3031,6 +3128,8 @@ class APIClient:
|
|
|
3031
3128
|
body["account_pool"] = account_pool
|
|
3032
3129
|
if metadata is not None:
|
|
3033
3130
|
body["metadata"] = metadata
|
|
3131
|
+
if reason is not None:
|
|
3132
|
+
body["reason"] = reason
|
|
3034
3133
|
return self._put(f"{self._PROFILES_BASE}/{quote(profile_id)}", body)
|
|
3035
3134
|
|
|
3036
3135
|
def delete_profile(self, profile_id: str) -> dict:
|
|
@@ -3053,8 +3152,8 @@ class APIClient:
|
|
|
3053
3152
|
) -> dict:
|
|
3054
3153
|
"""把某个 job/group 实例的访问权授予某 user/user_group(scope=resource 绑定)。
|
|
3055
3154
|
|
|
3056
|
-
workspace_id = 该实例所属 queue_id;
|
|
3057
|
-
|
|
3155
|
+
workspace_id = 该实例所属 queue_id;Job/Group Owner、Group Admin 或所属
|
|
3156
|
+
WorkSpace 的管理员/创建者及平台管理员可授权。鉴权走当前 API Key 解析出的调用者身份。
|
|
3058
3157
|
"""
|
|
3059
3158
|
body = {
|
|
3060
3159
|
"scope": "resource",
|
|
@@ -3320,7 +3419,7 @@ def _response_error_detail(resp: requests.Response) -> Any:
|
|
|
3320
3419
|
except Exception:
|
|
3321
3420
|
return resp.text
|
|
3322
3421
|
if isinstance(err_data, dict):
|
|
3323
|
-
return err_data.get("detail", resp.text)
|
|
3422
|
+
return err_data.get("detail", err_data.get("message", resp.text))
|
|
3324
3423
|
return err_data
|
|
3325
3424
|
|
|
3326
3425
|
|