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.
Files changed (21) hide show
  1. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/PKG-INFO +4 -4
  2. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/api.py +109 -5
  3. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/cli.py +210 -222
  4. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/exporter.py +33 -2
  5. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/managed_ak.py +60 -19
  6. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/profile_commands.py +109 -1
  7. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/pyproject.toml +4 -4
  8. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/__init__.py +0 -0
  9. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/akpool_commands.py +0 -0
  10. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/config.py +0 -0
  11. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/dataset_commands.py +0 -0
  12. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/dataset_register.py +0 -0
  13. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/fs_commands.py +0 -0
  14. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/instance_commands.py +0 -0
  15. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/irepo_sdk.py +0 -0
  16. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/manifest_view.py +0 -0
  17. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/repo_read_guard.py +0 -0
  18. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/share_api.py +0 -0
  19. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/split_publish.py +0 -0
  20. {ap_client-0.6.0.dev1 → ap_client-0.7.0.dev0}/ap_client/tbb.py +0 -0
  21. {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.6.0.dev1
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.1
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.2; extra == 'all'
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.2; extra == 'dataset'
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;apiserver 按该 WorkSpace 的 workspace-admin/
3052
- 创建者/superuser 门控。鉴权走当前 API Key 解析出的调用者身份。
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