ap-client 0.2.1.dev0__tar.gz → 0.2.2.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.2.1.dev0 → ap_client-0.2.2.dev1}/PKG-INFO +3 -3
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/api.py +125 -56
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/cli.py +60 -6
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/dataset_commands.py +252 -245
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/fs_commands.py +129 -21
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/instance_commands.py +59 -14
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/irepo_sdk.py +6 -6
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/profile_commands.py +1 -2
- ap_client-0.2.2.dev1/ap_client/split_publish.py +133 -0
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/pyproject.toml +3 -3
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/__init__.py +0 -0
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/config.py +0 -0
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/exporter.py +0 -0
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.dev1}/ap_client/tbb.py +0 -0
- {ap_client-0.2.1.dev0 → ap_client-0.2.2.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.2.
|
|
3
|
+
Version: 0.2.2.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]>=1.0.
|
|
12
|
+
Requires-Dist: instance-repo[oss]>=1.0.9; extra == 'all'
|
|
13
13
|
Provides-Extra: dataset
|
|
14
|
-
Requires-Dist: instance-repo[oss]>=1.0.
|
|
14
|
+
Requires-Dist: instance-repo[oss]>=1.0.9; 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.
|
|
@@ -268,6 +268,20 @@ class APIError(Exception):
|
|
|
268
268
|
return f"API Error {self.status_code}: {self.detail} (request_id={self.request_id})"
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
class PaginationProtocolError(RuntimeError):
|
|
272
|
+
"""The server stopped honoring a pagination protocol already in use."""
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _ensure_next_token_honored(response: dict, *, next_token: Optional[str], endpoint: str) -> None:
|
|
276
|
+
# An old server ignores the unknown next_token param and answers with its
|
|
277
|
+
# default first page, so a `response.get("next_token")` loop would silently
|
|
278
|
+
# repeat that page and drop the rest of the collection.
|
|
279
|
+
if next_token and "next_token" not in response:
|
|
280
|
+
raise PaginationProtocolError(
|
|
281
|
+
f"server stopped returning next_token while paginating {endpoint}; upgrade the server"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
271
285
|
def _secret_ws_params(workspace_id: Optional[str]) -> Optional[dict]:
|
|
272
286
|
"""Build the ``?workspace_id=`` query for name-addressed secret endpoints.
|
|
273
287
|
|
|
@@ -736,8 +750,35 @@ class APIClient:
|
|
|
736
750
|
return self._get(f"/templates/{quote(name)}", params=params)
|
|
737
751
|
|
|
738
752
|
def list_benchmarks(self) -> list:
|
|
739
|
-
"""List all
|
|
740
|
-
|
|
753
|
+
"""List all benchmarks from the central registry, preserving legacy IDs."""
|
|
754
|
+
benchmarks = []
|
|
755
|
+
page = 1
|
|
756
|
+
page_size = 200
|
|
757
|
+
# 与 CLI dataset list 的自动翻页同口径:100 页上限,防分页异常导致死循环。
|
|
758
|
+
max_pages = 100
|
|
759
|
+
while page <= max_pages:
|
|
760
|
+
payload = self._get(
|
|
761
|
+
"/apis/v1/benchmarks", params={"page": page, "page_size": page_size}
|
|
762
|
+
)
|
|
763
|
+
records, pagination = _split_paged(payload)
|
|
764
|
+
for record in records:
|
|
765
|
+
item = dict(record)
|
|
766
|
+
if "source_id" in item:
|
|
767
|
+
item.setdefault("id", item["source_id"])
|
|
768
|
+
benchmarks.append(item)
|
|
769
|
+
if not records or not pagination:
|
|
770
|
+
break
|
|
771
|
+
try:
|
|
772
|
+
total = int(pagination.get("total"))
|
|
773
|
+
except (TypeError, ValueError):
|
|
774
|
+
total = None
|
|
775
|
+
if total is not None:
|
|
776
|
+
if len(benchmarks) >= total:
|
|
777
|
+
break
|
|
778
|
+
elif len(records) < page_size:
|
|
779
|
+
break
|
|
780
|
+
page += 1
|
|
781
|
+
return benchmarks
|
|
741
782
|
|
|
742
783
|
def get_benchmark(self, name: str) -> dict:
|
|
743
784
|
"""Get a single benchmark by exact name."""
|
|
@@ -929,10 +970,6 @@ class APIClient:
|
|
|
929
970
|
params["environment"] = environment
|
|
930
971
|
return _unwrap_data(self._get(f"{self._DATASET_SERIES_BASE}/detail", params=params))
|
|
931
972
|
|
|
932
|
-
def create_dataset_series(self, body: dict) -> dict:
|
|
933
|
-
"""创建 dataset series。owner 由服务端按调用者身份填充。"""
|
|
934
|
-
return _unwrap_data(self._post(self._DATASET_SERIES_BASE, body))
|
|
935
|
-
|
|
936
973
|
def update_dataset_series(self, dataset_name: str, body: dict) -> dict:
|
|
937
974
|
"""PATCH dataset series 元数据(visibility/status 需要 dataset-admin)。"""
|
|
938
975
|
return _unwrap_data(
|
|
@@ -944,12 +981,6 @@ class APIClient:
|
|
|
944
981
|
)
|
|
945
982
|
)
|
|
946
983
|
|
|
947
|
-
def claim_dataset_series(self, dataset_id: str, body: dict) -> dict:
|
|
948
|
-
"""认领 dataset series(``workspace_id`` 服务端必填);成功后调用方成为 dataset admin。"""
|
|
949
|
-
return _unwrap_data(
|
|
950
|
-
self._post(f"{self._DATASET_SERIES_BASE}/{quote(dataset_id, safe='')}/claim", body)
|
|
951
|
-
)
|
|
952
|
-
|
|
953
984
|
def list_claim_workspaces(self) -> list[dict]:
|
|
954
985
|
"""列出当前用户可用于认领的 workspace。"""
|
|
955
986
|
return _as_record_list(self._get("/apis/v1/datasets/claim-workspaces"))
|
|
@@ -989,31 +1020,6 @@ class APIClient:
|
|
|
989
1020
|
params["environment"] = environment
|
|
990
1021
|
return _unwrap_data(self._get(f"{self._DATASET_VERSIONS_BASE}/detail", params=params))
|
|
991
1022
|
|
|
992
|
-
def create_dataset_series_version(self, dataset_name: str, body: dict) -> dict:
|
|
993
|
-
"""创建版本(``version``/``storage_type``/``storage_path``/``splits``/``status``)。"""
|
|
994
|
-
return _unwrap_data(
|
|
995
|
-
self._request(
|
|
996
|
-
"POST",
|
|
997
|
-
self._DATASET_VERSIONS_BASE,
|
|
998
|
-
params={"dataset_name": dataset_name},
|
|
999
|
-
json_body=body,
|
|
1000
|
-
)
|
|
1001
|
-
)
|
|
1002
|
-
|
|
1003
|
-
def update_dataset_series_version(self, dataset_name: str, version: str, body: dict) -> dict:
|
|
1004
|
-
"""PATCH 版本(``status``/``splits``/``manifest``/``run_type``/``split_run_types``)。
|
|
1005
|
-
|
|
1006
|
-
服务端要求 ``splits`` 与 ``split_run_types`` 互斥,调用方需分两次 PATCH。
|
|
1007
|
-
"""
|
|
1008
|
-
return _unwrap_data(
|
|
1009
|
-
self._request(
|
|
1010
|
-
"PATCH",
|
|
1011
|
-
f"{self._DATASET_VERSIONS_BASE}/detail",
|
|
1012
|
-
params={"dataset_name": dataset_name, "version": version},
|
|
1013
|
-
json_body=body,
|
|
1014
|
-
)
|
|
1015
|
-
)
|
|
1016
|
-
|
|
1017
1023
|
# ---- dataset instances (metadata only) ----
|
|
1018
1024
|
|
|
1019
1025
|
def list_dataset_series_instances(
|
|
@@ -1062,6 +1068,19 @@ class APIClient:
|
|
|
1062
1068
|
params["environment"] = environment
|
|
1063
1069
|
return _unwrap_data(self._get(f"{self._DATASET_INSTANCES_BASE}/detail", params=params))
|
|
1064
1070
|
|
|
1071
|
+
def get_dataset_series_split(self, dataset_name: str, version: str, split: str) -> dict:
|
|
1072
|
+
"""Read the selected split's lifecycle without a version-level aggregate."""
|
|
1073
|
+
return _unwrap_data(
|
|
1074
|
+
self._get(
|
|
1075
|
+
"/apis/v1/datasets/splits/detail",
|
|
1076
|
+
params={"dataset_name": dataset_name, "version": version, "split": split},
|
|
1077
|
+
)
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
def get_dataset_split_release(self, workflow_id: str) -> dict:
|
|
1081
|
+
"""Read a release workflow, including approval and per-step outcomes."""
|
|
1082
|
+
return _unwrap_data(self._get(f"/apis/v1/workflows/{quote(workflow_id, safe='')}"))
|
|
1083
|
+
|
|
1065
1084
|
# ---- permissions ----
|
|
1066
1085
|
|
|
1067
1086
|
def get_my_resource_permissions(self, resource_type: str, resource_id: str) -> list[str]:
|
|
@@ -1514,6 +1533,9 @@ class APIClient:
|
|
|
1514
1533
|
meta_job_type: Optional[str] = None,
|
|
1515
1534
|
upstream_platform: Optional[str] = None,
|
|
1516
1535
|
upstream_job_id: Optional[str] = None,
|
|
1536
|
+
next_token: Optional[str] = None,
|
|
1537
|
+
pagination: Optional[str] = None,
|
|
1538
|
+
include_total: bool = True,
|
|
1517
1539
|
) -> dict:
|
|
1518
1540
|
"""List jobs."""
|
|
1519
1541
|
params = {"skip": skip, "limit": limit}
|
|
@@ -1571,7 +1593,15 @@ class APIClient:
|
|
|
1571
1593
|
params["created_at_sort"] = created_at_sort
|
|
1572
1594
|
if finished_at_sort is not None:
|
|
1573
1595
|
params["finished_at_sort"] = finished_at_sort
|
|
1574
|
-
|
|
1596
|
+
if next_token:
|
|
1597
|
+
params["next_token"] = next_token
|
|
1598
|
+
if pagination:
|
|
1599
|
+
params["pagination"] = pagination
|
|
1600
|
+
if not include_total:
|
|
1601
|
+
params["include_total"] = False
|
|
1602
|
+
response = self._get("/jobs", params=params)
|
|
1603
|
+
_ensure_next_token_honored(response, next_token=next_token, endpoint="/jobs")
|
|
1604
|
+
return response
|
|
1575
1605
|
|
|
1576
1606
|
# ==================== Group operations ====================
|
|
1577
1607
|
|
|
@@ -1762,6 +1792,9 @@ class APIClient:
|
|
|
1762
1792
|
meta_job_type: Optional[str] = None,
|
|
1763
1793
|
upstream_platform: Optional[str] = None,
|
|
1764
1794
|
upstream_job_id: Optional[str] = None,
|
|
1795
|
+
next_token: Optional[str] = None,
|
|
1796
|
+
pagination: Optional[str] = None,
|
|
1797
|
+
include_total: bool = True,
|
|
1765
1798
|
) -> dict:
|
|
1766
1799
|
"""List jobs in a group."""
|
|
1767
1800
|
params: dict = {"skip": skip, "limit": limit}
|
|
@@ -1785,7 +1818,16 @@ class APIClient:
|
|
|
1785
1818
|
params["upstream_job_id"] = upstream_job_id
|
|
1786
1819
|
if include_post_process:
|
|
1787
1820
|
params["include_post_process"] = True
|
|
1788
|
-
|
|
1821
|
+
if next_token:
|
|
1822
|
+
params["next_token"] = next_token
|
|
1823
|
+
if pagination:
|
|
1824
|
+
params["pagination"] = pagination
|
|
1825
|
+
if not include_total:
|
|
1826
|
+
params["include_total"] = False
|
|
1827
|
+
endpoint = f"/groups/{quote(group_id)}/jobs"
|
|
1828
|
+
response = self._get(endpoint, params=params)
|
|
1829
|
+
_ensure_next_token_honored(response, next_token=next_token, endpoint=endpoint)
|
|
1830
|
+
return response
|
|
1789
1831
|
|
|
1790
1832
|
def get_group_post_process_job_id(
|
|
1791
1833
|
self, group_id: str, timeout: TimeoutType = None
|
|
@@ -2263,19 +2305,30 @@ class APIClient:
|
|
|
2263
2305
|
skip: int = 0,
|
|
2264
2306
|
limit: int = _GROUP_ARTIFACTS_PAGE_SIZE,
|
|
2265
2307
|
include_post_process: bool = False,
|
|
2308
|
+
next_token: Optional[str] = None,
|
|
2309
|
+
pagination: Optional[str] = None,
|
|
2310
|
+
include_total: bool = True,
|
|
2266
2311
|
) -> dict:
|
|
2267
2312
|
"""Get one page of artifact download links for a group."""
|
|
2268
2313
|
params: dict[str, object] = {"skip": skip, "limit": limit}
|
|
2269
2314
|
if include_post_process:
|
|
2270
2315
|
params["include_post_process"] = True
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2316
|
+
if next_token:
|
|
2317
|
+
params["next_token"] = next_token
|
|
2318
|
+
if pagination:
|
|
2319
|
+
params["pagination"] = pagination
|
|
2320
|
+
if not include_total:
|
|
2321
|
+
params["include_total"] = False
|
|
2322
|
+
endpoint = f"/groups/{quote(group_id)}/artifacts"
|
|
2323
|
+
response = self._get(endpoint, params=params)
|
|
2324
|
+
_ensure_next_token_honored(response, next_token=next_token, endpoint=endpoint)
|
|
2325
|
+
return response
|
|
2275
2326
|
|
|
2276
2327
|
def get_group_artifacts(self, group_id: str, include_post_process: bool = False) -> dict:
|
|
2277
2328
|
"""Get artifact download links for a group."""
|
|
2329
|
+
next_token: str | None = None
|
|
2278
2330
|
skip = 0
|
|
2331
|
+
mode: str | None = None
|
|
2279
2332
|
artifacts: list[dict] = []
|
|
2280
2333
|
last_page: dict | None = None
|
|
2281
2334
|
|
|
@@ -2285,25 +2338,41 @@ class APIClient:
|
|
|
2285
2338
|
skip=skip,
|
|
2286
2339
|
limit=_GROUP_ARTIFACTS_PAGE_SIZE,
|
|
2287
2340
|
include_post_process=include_post_process,
|
|
2341
|
+
next_token=next_token,
|
|
2342
|
+
pagination="cursor" if mode in {None, "cursor"} else None,
|
|
2343
|
+
include_total=False,
|
|
2288
2344
|
)
|
|
2289
2345
|
last_page = page
|
|
2290
2346
|
page_artifacts = page.get("artifacts") or []
|
|
2291
2347
|
artifacts.extend(page_artifacts)
|
|
2348
|
+
has_token_field = "next_token" in page
|
|
2292
2349
|
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
break
|
|
2296
|
-
if len(page_artifacts) < _GROUP_ARTIFACTS_PAGE_SIZE:
|
|
2297
|
-
break
|
|
2298
|
-
|
|
2299
|
-
skip += len(page_artifacts)
|
|
2350
|
+
if mode is None:
|
|
2351
|
+
mode = "cursor" if has_token_field else "offset"
|
|
2300
2352
|
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2353
|
+
if mode == "cursor":
|
|
2354
|
+
next_token = page.get("next_token")
|
|
2355
|
+
if not next_token:
|
|
2356
|
+
break
|
|
2357
|
+
else:
|
|
2358
|
+
skip += len(page_artifacts)
|
|
2359
|
+
total = page.get("total")
|
|
2360
|
+
if not page_artifacts or len(page_artifacts) < _GROUP_ARTIFACTS_PAGE_SIZE:
|
|
2361
|
+
break
|
|
2362
|
+
if total is not None and skip >= total:
|
|
2363
|
+
break
|
|
2364
|
+
|
|
2365
|
+
result = dict(last_page or {})
|
|
2366
|
+
result.pop("next_token", None)
|
|
2367
|
+
result.update(
|
|
2368
|
+
{
|
|
2369
|
+
"total": len(artifacts),
|
|
2370
|
+
"skip": 0,
|
|
2371
|
+
"limit": len(artifacts),
|
|
2372
|
+
"artifacts": artifacts,
|
|
2373
|
+
}
|
|
2374
|
+
)
|
|
2375
|
+
return result
|
|
2307
2376
|
|
|
2308
2377
|
def get_job_artifacts(self, job_ids: list) -> list:
|
|
2309
2378
|
"""Get artifact download links for one or more jobs."""
|
|
@@ -16,6 +16,7 @@ from ap_client import __version__, get_client, get_config, tbb
|
|
|
16
16
|
from ap_client.api import (
|
|
17
17
|
_OTHERS_MODEL,
|
|
18
18
|
APIError,
|
|
19
|
+
PaginationProtocolError,
|
|
19
20
|
_merge_meta_tags,
|
|
20
21
|
set_cluster_override,
|
|
21
22
|
set_verbose_override,
|
|
@@ -928,10 +929,7 @@ def _print_params_plain(params: Any, masked_params: list) -> None:
|
|
|
928
929
|
rows: list[tuple[str, Any]] = []
|
|
929
930
|
for key, value in params.items():
|
|
930
931
|
display_value = _format_plain_value(value)
|
|
931
|
-
|
|
932
|
-
# sk-***890 / ****),不能再按值严格等于 "***" 判断,否则形态变化后
|
|
933
|
-
# (masked) 标识静默丢失
|
|
934
|
-
if str(key) in masked_paths:
|
|
932
|
+
if str(key) in masked_paths and value == "***":
|
|
935
933
|
display_value = f"{display_value} (masked)"
|
|
936
934
|
rows.append((str(key), display_value))
|
|
937
935
|
_print_key_values(rows, skip_empty=False)
|
|
@@ -2636,6 +2634,21 @@ def job_list(
|
|
|
2636
2634
|
desc: bool = typer.Option(False, "--desc", help="Sort descending; requires --sort"),
|
|
2637
2635
|
skip: int = typer.Option(0, "--skip", help="Skip the first N entries"),
|
|
2638
2636
|
limit: int = typer.Option(100, "--limit", help="Maximum number of entries to return"),
|
|
2637
|
+
next_token: Optional[str] = typer.Option(
|
|
2638
|
+
None,
|
|
2639
|
+
"--next-token",
|
|
2640
|
+
help="Opaque token returned by the previous exact-group job list page; requires --group-id",
|
|
2641
|
+
),
|
|
2642
|
+
cursor: bool = typer.Option(
|
|
2643
|
+
False,
|
|
2644
|
+
"--cursor",
|
|
2645
|
+
help="Opt this page into next-token pagination; requires --group-id",
|
|
2646
|
+
),
|
|
2647
|
+
no_total: bool = typer.Option(
|
|
2648
|
+
False,
|
|
2649
|
+
"--no-total",
|
|
2650
|
+
help="Skip the server-side count; total becomes null in json/yaml output",
|
|
2651
|
+
),
|
|
2639
2652
|
columns: Optional[str] = typer.Option(
|
|
2640
2653
|
None,
|
|
2641
2654
|
"--columns",
|
|
@@ -2661,7 +2674,16 @@ def job_list(
|
|
|
2661
2674
|
raise typer.BadParameter("--tag-pattern cannot be used with --tag")
|
|
2662
2675
|
if normalized_tag_logic != "and" and not tag:
|
|
2663
2676
|
raise typer.BadParameter("--tag-logic requires --tag")
|
|
2677
|
+
if next_token and not group_id:
|
|
2678
|
+
raise typer.BadParameter("--next-token requires --group-id")
|
|
2679
|
+
if cursor and not group_id:
|
|
2680
|
+
raise typer.BadParameter("--cursor requires --group-id")
|
|
2681
|
+
if next_token and skip > 0:
|
|
2682
|
+
raise typer.BadParameter("--next-token cannot be used with --skip greater than 0")
|
|
2683
|
+
if cursor and skip > 0:
|
|
2684
|
+
raise typer.BadParameter("--cursor cannot be used with --skip greater than 0")
|
|
2664
2685
|
sort_kwargs = _job_list_sort_kwargs(sort, asc=asc, desc=desc)
|
|
2686
|
+
cursor_request = bool(next_token) or cursor
|
|
2665
2687
|
client = get_client()
|
|
2666
2688
|
result = client.list_jobs(
|
|
2667
2689
|
template=template,
|
|
@@ -2685,6 +2707,9 @@ def job_list(
|
|
|
2685
2707
|
**({"meta_model": meta_model} if meta_model is not None else {}),
|
|
2686
2708
|
**({"meta_job_type": meta_job_type} if meta_job_type is not None else {}),
|
|
2687
2709
|
**sort_kwargs,
|
|
2710
|
+
**({"next_token": next_token} if next_token else {}),
|
|
2711
|
+
**({"pagination": "cursor"} if cursor_request else {}),
|
|
2712
|
+
include_total=not no_total,
|
|
2688
2713
|
skip=skip,
|
|
2689
2714
|
limit=limit,
|
|
2690
2715
|
)
|
|
@@ -2694,6 +2719,9 @@ def job_list(
|
|
|
2694
2719
|
_print_job_list_plain(result, columns=selected_columns)
|
|
2695
2720
|
else:
|
|
2696
2721
|
_print_formatted(result, output_format)
|
|
2722
|
+
next_token = result.get("next_token")
|
|
2723
|
+
if output_format in {"table", "plain"} and next_token:
|
|
2724
|
+
typer.echo(f"Next token: {next_token}", err=True)
|
|
2697
2725
|
|
|
2698
2726
|
|
|
2699
2727
|
@job_app.command("create")
|
|
@@ -4751,7 +4779,7 @@ def benchmark_list(
|
|
|
4751
4779
|
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
4752
4780
|
),
|
|
4753
4781
|
):
|
|
4754
|
-
"""List benchmarks from the
|
|
4782
|
+
"""List benchmarks from the central benchmark registry."""
|
|
4755
4783
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
4756
4784
|
client = get_client()
|
|
4757
4785
|
result = client.list_benchmarks()
|
|
@@ -5746,6 +5774,21 @@ def group_jobs(
|
|
|
5746
5774
|
),
|
|
5747
5775
|
skip: int = typer.Option(0, "--skip", help="Skip the first N entries"),
|
|
5748
5776
|
limit: int = typer.Option(100, "--limit", help="Maximum number of entries to return"),
|
|
5777
|
+
next_token: Optional[str] = typer.Option(
|
|
5778
|
+
None,
|
|
5779
|
+
"--next-token",
|
|
5780
|
+
help="Opaque token returned by the previous Group jobs page",
|
|
5781
|
+
),
|
|
5782
|
+
cursor: bool = typer.Option(
|
|
5783
|
+
False,
|
|
5784
|
+
"--cursor",
|
|
5785
|
+
help="Opt this page into next-token pagination",
|
|
5786
|
+
),
|
|
5787
|
+
no_total: bool = typer.Option(
|
|
5788
|
+
False,
|
|
5789
|
+
"--no-total",
|
|
5790
|
+
help="Skip the server-side count; total becomes null in json/yaml output",
|
|
5791
|
+
),
|
|
5749
5792
|
output_format: str = typer.Option(
|
|
5750
5793
|
None,
|
|
5751
5794
|
"--format",
|
|
@@ -5754,6 +5797,11 @@ def group_jobs(
|
|
|
5754
5797
|
):
|
|
5755
5798
|
"""List jobs in a Group."""
|
|
5756
5799
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
5800
|
+
if next_token and skip > 0:
|
|
5801
|
+
raise typer.BadParameter("--next-token cannot be used with --skip greater than 0")
|
|
5802
|
+
if cursor and skip > 0:
|
|
5803
|
+
raise typer.BadParameter("--cursor cannot be used with --skip greater than 0")
|
|
5804
|
+
cursor_request = bool(next_token) or cursor
|
|
5757
5805
|
client = get_client()
|
|
5758
5806
|
result = client.list_group_jobs(
|
|
5759
5807
|
group_id,
|
|
@@ -5761,6 +5809,9 @@ def group_jobs(
|
|
|
5761
5809
|
include_post_process=include_post_process,
|
|
5762
5810
|
skip=skip,
|
|
5763
5811
|
limit=limit,
|
|
5812
|
+
**({"next_token": next_token} if next_token else {}),
|
|
5813
|
+
**({"pagination": "cursor"} if cursor_request else {}),
|
|
5814
|
+
include_total=not no_total,
|
|
5764
5815
|
)
|
|
5765
5816
|
if output_format == "table":
|
|
5766
5817
|
_print_job_list_plain(result, rich=True)
|
|
@@ -5768,6 +5819,9 @@ def group_jobs(
|
|
|
5768
5819
|
_print_job_list_plain(result)
|
|
5769
5820
|
else:
|
|
5770
5821
|
_print_formatted(result, output_format)
|
|
5822
|
+
following_token = result.get("next_token")
|
|
5823
|
+
if output_format in {"table", "plain"} and following_token:
|
|
5824
|
+
typer.echo(f"Next token: {following_token}", err=True)
|
|
5771
5825
|
|
|
5772
5826
|
|
|
5773
5827
|
@group_app.command("stats")
|
|
@@ -6038,7 +6092,7 @@ def main() -> None:
|
|
|
6038
6092
|
except APIError as exc:
|
|
6039
6093
|
typer.echo(_format_api_error(exc), err=True)
|
|
6040
6094
|
raise SystemExit(1) from None
|
|
6041
|
-
except ConfigurationError as exc:
|
|
6095
|
+
except (ConfigurationError, PaginationProtocolError) as exc:
|
|
6042
6096
|
typer.echo(f"error: {exc}", err=True)
|
|
6043
6097
|
raise SystemExit(1) from None
|
|
6044
6098
|
|