ap-client 0.2.2.dev0__tar.gz → 0.2.2.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.2.2.dev0 → ap_client-0.2.2.dev2}/PKG-INFO +1 -1
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/api.py +29 -1
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/dataset_commands.py +148 -23
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/pyproject.toml +1 -1
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/__init__.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/cli.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/config.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/exporter.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/fs_commands.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/instance_commands.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/irepo_sdk.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/profile_commands.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/split_publish.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/tbb.py +0 -0
- {ap_client-0.2.2.dev0 → ap_client-0.2.2.dev2}/ap_client/waiter.py +0 -0
|
@@ -935,6 +935,11 @@ class APIClient:
|
|
|
935
935
|
keyword: Optional[str] = None,
|
|
936
936
|
visibility: Optional[str] = None,
|
|
937
937
|
benchmark_id: Optional[str] = None,
|
|
938
|
+
owned_by_me: bool = False,
|
|
939
|
+
updated_from: Optional[str] = None,
|
|
940
|
+
updated_to: Optional[str] = None,
|
|
941
|
+
sort_by: Optional[str] = None,
|
|
942
|
+
order: Optional[str] = None,
|
|
938
943
|
include_deprecated: bool = False,
|
|
939
944
|
environment: Optional[str] = None,
|
|
940
945
|
page: int = 1,
|
|
@@ -942,7 +947,20 @@ class APIClient:
|
|
|
942
947
|
) -> dict:
|
|
943
948
|
"""一页 dataset series(原始 PagedSuccess envelope)。
|
|
944
949
|
|
|
945
|
-
|
|
950
|
+
服务端在**分页之前**完成筛选与排序,所以响应里的 ``total`` 就是过滤后的计数
|
|
951
|
+
(``docs/openapi.json`` 的 ``GET /datasets/series``)。可用过滤参数:
|
|
952
|
+
|
|
953
|
+
* ``q``/``keyword``:对 ``dataset_name`` + ``display_name`` 模糊匹配
|
|
954
|
+
(``keyword`` 是 ``q`` 的废弃别名,仅当 ``q`` 为空时生效);
|
|
955
|
+
* ``visibility``:逗号分隔多值,同维 OR(逐项 ``public``/``private``);
|
|
956
|
+
* ``benchmark_id``:精确匹配绑定,特殊值 ``none`` 表示未绑定;
|
|
957
|
+
* ``owned_by_me``:只保留 owner == 调用方身份的 dataset(需登录态);
|
|
958
|
+
* ``updated_from``/``updated_to``:``updated_at`` 闭区间(RFC3339,可只给一侧);
|
|
959
|
+
* ``environment``:默认 ``online``。
|
|
960
|
+
|
|
961
|
+
排序用 ``sort_by``(``updated_at``/``dataset_name``)+``order``(``asc``/``desc``)。
|
|
962
|
+
**没有按任意 uid 过滤 owner 的参数**:``owned_by_me`` 只能是"我自己",查别人的
|
|
963
|
+
dataset 仍要在客户端过滤。非法取值由服务端 400 拒绝。
|
|
946
964
|
"""
|
|
947
965
|
params: dict = {"page": page, "page_size": page_size}
|
|
948
966
|
if q:
|
|
@@ -953,6 +971,16 @@ class APIClient:
|
|
|
953
971
|
params["visibility"] = visibility
|
|
954
972
|
if benchmark_id:
|
|
955
973
|
params["benchmark_id"] = benchmark_id
|
|
974
|
+
if owned_by_me:
|
|
975
|
+
params["owned_by_me"] = "true"
|
|
976
|
+
if updated_from:
|
|
977
|
+
params["updated_from"] = updated_from
|
|
978
|
+
if updated_to:
|
|
979
|
+
params["updated_to"] = updated_to
|
|
980
|
+
if sort_by:
|
|
981
|
+
params["sort_by"] = sort_by
|
|
982
|
+
if order:
|
|
983
|
+
params["order"] = order
|
|
956
984
|
if include_deprecated:
|
|
957
985
|
params["include_deprecated"] = "true"
|
|
958
986
|
if environment:
|
|
@@ -8,10 +8,14 @@ is implemented in split_publish.py and never publishes a whole version.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
-
|
|
11
|
+
import re
|
|
12
|
+
from typing import TYPE_CHECKING, Any, List, Optional
|
|
12
13
|
|
|
13
14
|
import typer
|
|
14
15
|
|
|
16
|
+
if TYPE_CHECKING: # pragma: no cover - 仅用于类型标注,运行时不 import
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
|
|
15
19
|
__all__ = [
|
|
16
20
|
"dataset_version_app",
|
|
17
21
|
"dataset_split_app",
|
|
@@ -104,6 +108,11 @@ _VISIBILITIES = ("private", "public")
|
|
|
104
108
|
#: 服务端 version 枚举(设计 §9:上游的 publishing/failed 是 ingest 任务状态,不在此列)。
|
|
105
109
|
_VERSION_STATUSES = ("draft", "published", "deprecated", "archived")
|
|
106
110
|
_RUN_TYPES = ("train", "eval")
|
|
111
|
+
#: ``dataset list`` 的服务端排序键与方向(``GET /apis/v1/datasets/series``)。
|
|
112
|
+
_SORT_KEYS = ("updated_at", "dataset_name")
|
|
113
|
+
_SORT_ORDERS = ("asc", "desc")
|
|
114
|
+
#: 服务端 ``benchmark_id`` 的特殊取值:未绑定 benchmark 的治理查询。
|
|
115
|
+
_NO_BENCHMARK = "none"
|
|
107
116
|
|
|
108
117
|
_FORMAT_HELP = "Output format: plain/table/json/yaml (default: AP_FORMAT or command default)"
|
|
109
118
|
|
|
@@ -274,6 +283,71 @@ def _resolve_benchmark_id(client, value: str) -> str:
|
|
|
274
283
|
)
|
|
275
284
|
|
|
276
285
|
|
|
286
|
+
def _visibility_values(values: Optional[List[str]]) -> list[str]:
|
|
287
|
+
"""``--visibility`` 归一成服务端的逗号分隔多值形式(同维 OR)。
|
|
288
|
+
|
|
289
|
+
既支持重复给(``--visibility public --visibility private``),也支持逗号分隔
|
|
290
|
+
(``--visibility public,private``);逐项校验枚举,非法值 exit 2,不等到服务端 400。
|
|
291
|
+
"""
|
|
292
|
+
resolved: list[str] = []
|
|
293
|
+
for raw in values or []:
|
|
294
|
+
for part in str(raw).split(","):
|
|
295
|
+
part = part.strip()
|
|
296
|
+
if not part:
|
|
297
|
+
continue
|
|
298
|
+
checked = _check_choice(part, _VISIBILITIES, "--visibility")
|
|
299
|
+
if checked not in resolved:
|
|
300
|
+
resolved.append(checked)
|
|
301
|
+
return resolved
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
#: ``--updated-from/--updated-to`` 的 RFC3339 形状:日期 + 分隔符 + 时间 + 可选小数秒 + 带
|
|
305
|
+
#: 偏移的时区(与 ``time.Parse(time.RFC3339, ...)`` 的接受面基本一致)。
|
|
306
|
+
_UPDATED_AT_PATTERN = re.compile(
|
|
307
|
+
r"^(\d{4}-\d{2}-\d{2})[Tt ](\d{2}:\d{2}:\d{2})(\.\d+)?([Zz]|[+-]\d{2}:?\d{2})$"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _parse_updated_at(option: str, raw: str) -> "datetime":
|
|
312
|
+
"""把 ``--updated-from/--updated-to`` 解析成带时区的 ``datetime``;形状不对则 exit 2。
|
|
313
|
+
|
|
314
|
+
只做前置校验,下发给服务端的仍是用户原样输入的字符串——服务端的
|
|
315
|
+
``datasetUpdatedRangeFromQuery`` 才是最终解释方(它会把入参归一到 UTC 并做同样的
|
|
316
|
+
区间倒挂检查)。小数秒超过 6 位时截断,避免 Python <3.11 的 ``fromisoformat`` 把
|
|
317
|
+
服务端能接受的纳秒时间戳误判成非法。
|
|
318
|
+
"""
|
|
319
|
+
from datetime import datetime
|
|
320
|
+
|
|
321
|
+
match = _UPDATED_AT_PATTERN.match(raw)
|
|
322
|
+
if match is None:
|
|
323
|
+
raise _fail(
|
|
324
|
+
f"{option} 取值非法:{raw!r}",
|
|
325
|
+
"用带时区的 RFC3339 时间戳,例如 2026-03-01T00:00:00Z 或 2026-03-02T12:30:00+08:00",
|
|
326
|
+
exit_code=2,
|
|
327
|
+
)
|
|
328
|
+
date, clock, fraction, zone = match.groups()
|
|
329
|
+
fraction = (fraction or "")[:7] # ".123456789" → ".123456"
|
|
330
|
+
zone = "+00:00" if zone in ("Z", "z") else zone
|
|
331
|
+
if len(zone) == 5: # "+0800" → "+08:00"
|
|
332
|
+
zone = f"{zone[:3]}:{zone[3:]}"
|
|
333
|
+
return datetime.fromisoformat(f"{date}T{clock}{fraction}{zone}")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _updated_range(from_value: Optional[str], to_value: Optional[str]) -> tuple[str, str]:
|
|
337
|
+
"""校验 ``--updated-from/--updated-to``(闭区间,可只给一侧),原样返回待下发字符串。"""
|
|
338
|
+
lower = str(from_value or "").strip()
|
|
339
|
+
upper = str(to_value or "").strip()
|
|
340
|
+
parsed_lower = _parse_updated_at("--updated-from", lower) if lower else None
|
|
341
|
+
parsed_upper = _parse_updated_at("--updated-to", upper) if upper else None
|
|
342
|
+
if parsed_lower is not None and parsed_upper is not None and parsed_lower > parsed_upper:
|
|
343
|
+
raise _fail(
|
|
344
|
+
"--updated-from 不能晚于 --updated-to",
|
|
345
|
+
"区间是闭区间;调换两个时间戳,或只给一侧",
|
|
346
|
+
exit_code=2,
|
|
347
|
+
)
|
|
348
|
+
return lower, upper
|
|
349
|
+
|
|
350
|
+
|
|
277
351
|
def _dataset_id_or_fail(client, dataset: str, *, stage: str) -> tuple[str, dict]:
|
|
278
352
|
"""取 ``dataset_id``(access/claim 都要),失败按设计 §5 的固定文案退出。"""
|
|
279
353
|
from .api import APIError
|
|
@@ -493,13 +567,38 @@ def dataset_list(
|
|
|
493
567
|
None, "--query", help="Fuzzy match on dataset name / description"
|
|
494
568
|
),
|
|
495
569
|
benchmark: Optional[str] = typer.Option(
|
|
496
|
-
None,
|
|
570
|
+
None,
|
|
571
|
+
"--benchmark",
|
|
572
|
+
help=f"Benchmark name or source_id; '{_NO_BENCHMARK}' selects datasets with no binding",
|
|
497
573
|
),
|
|
498
|
-
visibility: Optional[str] = typer.Option(
|
|
499
|
-
None,
|
|
574
|
+
visibility: Optional[List[str]] = typer.Option(
|
|
575
|
+
None,
|
|
576
|
+
"--visibility",
|
|
577
|
+
help=f"Filter by visibility: {'/'.join(_VISIBILITIES)}; repeat or comma-separate for OR",
|
|
578
|
+
),
|
|
579
|
+
mine: bool = typer.Option(
|
|
580
|
+
False,
|
|
581
|
+
"--mine",
|
|
582
|
+
help="Only datasets owned by the caller (server-side owned_by_me=true)",
|
|
500
583
|
),
|
|
501
584
|
owner: Optional[str] = typer.Option(
|
|
502
|
-
None,
|
|
585
|
+
None,
|
|
586
|
+
"--owner",
|
|
587
|
+
help="Filter by owner uid (client-side; the server only filters by the caller: --mine)",
|
|
588
|
+
),
|
|
589
|
+
updated_from: Optional[str] = typer.Option(
|
|
590
|
+
None,
|
|
591
|
+
"--updated-from",
|
|
592
|
+
help="Inclusive RFC3339 lower bound on updated_at (metadata edits only)",
|
|
593
|
+
),
|
|
594
|
+
updated_to: Optional[str] = typer.Option(
|
|
595
|
+
None, "--updated-to", help="Inclusive RFC3339 upper bound on updated_at"
|
|
596
|
+
),
|
|
597
|
+
sort_by: Optional[str] = typer.Option(
|
|
598
|
+
None, "--sort-by", help=f"Sort key: {'/'.join(_SORT_KEYS)} (server default: updated_at)"
|
|
599
|
+
),
|
|
600
|
+
order: Optional[str] = typer.Option(
|
|
601
|
+
None, "--order", help=f"Sort direction: {'/'.join(_SORT_ORDERS)} (server default: desc)"
|
|
503
602
|
),
|
|
504
603
|
page: Optional[int] = typer.Option(
|
|
505
604
|
None,
|
|
@@ -548,7 +647,12 @@ def dataset_list(
|
|
|
548
647
|
for name, value in (
|
|
549
648
|
("--benchmark", benchmark),
|
|
550
649
|
("--visibility", visibility),
|
|
650
|
+
("--mine", mine),
|
|
551
651
|
("--owner", owner),
|
|
652
|
+
("--updated-from", updated_from),
|
|
653
|
+
("--updated-to", updated_to),
|
|
654
|
+
("--sort-by", sort_by),
|
|
655
|
+
("--order", order),
|
|
552
656
|
("--page", page),
|
|
553
657
|
("--page-size", page_size),
|
|
554
658
|
)
|
|
@@ -570,21 +674,43 @@ def dataset_list(
|
|
|
570
674
|
_print_formatted(result, output_format)
|
|
571
675
|
return
|
|
572
676
|
|
|
573
|
-
resolved_visibility = (
|
|
574
|
-
|
|
575
|
-
|
|
677
|
+
resolved_visibility = _visibility_values(visibility)
|
|
678
|
+
if mine and str(owner or "").strip():
|
|
679
|
+
# --mine 是服务端的 owned_by_me(只认调用方自己),与"查别人的 uid"互斥;
|
|
680
|
+
# 两个一起下发只会得到空结果,不如直接报用法错。
|
|
681
|
+
raise _fail(
|
|
682
|
+
"--mine 与 --owner 不能同时给出",
|
|
683
|
+
"--mine 查自己(服务端过滤);要查别人的 dataset 用 --owner <uid>(客户端过滤)",
|
|
684
|
+
exit_code=2,
|
|
685
|
+
)
|
|
686
|
+
resolved_sort_by = _check_choice(sort_by, _SORT_KEYS, "--sort-by") if sort_by else ""
|
|
687
|
+
resolved_order = _check_choice(order, _SORT_ORDERS, "--order") if order else ""
|
|
688
|
+
resolved_from, resolved_to = _updated_range(updated_from, updated_to)
|
|
689
|
+
|
|
576
690
|
client = get_client()
|
|
577
|
-
|
|
691
|
+
if str(benchmark or "").strip() == _NO_BENCHMARK:
|
|
692
|
+
# 服务端的治理查询:benchmark_id=none 表示"未绑定 benchmark",不经过名称解析。
|
|
693
|
+
benchmark_id = _NO_BENCHMARK
|
|
694
|
+
else:
|
|
695
|
+
benchmark_id = _resolve_benchmark_id(client, benchmark) if benchmark else ""
|
|
696
|
+
|
|
697
|
+
# 服务端在分页前完成筛选/排序,这些条件对每一页都成立,自动翻页可以直接复用。
|
|
698
|
+
filters: dict[str, Any] = {
|
|
699
|
+
"q": resolved_query or None,
|
|
700
|
+
"visibility": ",".join(resolved_visibility) or None,
|
|
701
|
+
"benchmark_id": benchmark_id or None,
|
|
702
|
+
"owned_by_me": mine,
|
|
703
|
+
"updated_from": resolved_from or None,
|
|
704
|
+
"updated_to": resolved_to or None,
|
|
705
|
+
"sort_by": resolved_sort_by or None,
|
|
706
|
+
"order": resolved_order or None,
|
|
707
|
+
}
|
|
578
708
|
|
|
579
709
|
resolved_page = page or 1
|
|
580
|
-
#
|
|
581
|
-
resolved_page_size = page_size or (
|
|
710
|
+
# 自动翻页用 100 条/页(500 对一次全量拉取偏大);显式分页仍是默认 20。
|
|
711
|
+
resolved_page_size = page_size or (100 if page is None else 20)
|
|
582
712
|
records, pagination = client.list_dataset_series(
|
|
583
|
-
|
|
584
|
-
visibility=resolved_visibility or None,
|
|
585
|
-
benchmark_id=benchmark_id or None,
|
|
586
|
-
page=resolved_page,
|
|
587
|
-
page_size=resolved_page_size,
|
|
713
|
+
page=resolved_page, page_size=resolved_page_size, **filters
|
|
588
714
|
)
|
|
589
715
|
records = [record for record in (records or []) if isinstance(record, dict)]
|
|
590
716
|
|
|
@@ -597,11 +723,7 @@ def dataset_list(
|
|
|
597
723
|
while records and (total is None or seen < total) and guard < 100:
|
|
598
724
|
guard += 1
|
|
599
725
|
more, pagination = client.list_dataset_series(
|
|
600
|
-
|
|
601
|
-
visibility=resolved_visibility or None,
|
|
602
|
-
benchmark_id=benchmark_id or None,
|
|
603
|
-
page=resolved_page + guard,
|
|
604
|
-
page_size=resolved_page_size,
|
|
726
|
+
page=resolved_page + guard, page_size=resolved_page_size, **filters
|
|
605
727
|
)
|
|
606
728
|
more = [record for record in (more or []) if isinstance(record, dict)]
|
|
607
729
|
if not more:
|
|
@@ -617,13 +739,16 @@ def dataset_list(
|
|
|
617
739
|
wanted_owner = str(owner or "").strip()
|
|
618
740
|
payload: dict[str, Any] = {}
|
|
619
741
|
if wanted_owner:
|
|
620
|
-
#
|
|
742
|
+
# 服务端只有 owned_by_me(按调用方身份过滤),没有任意 uid 的 owner 参数,
|
|
743
|
+
# 所以只能在客户端过滤,必须在 stderr 说清楚。
|
|
621
744
|
_note(
|
|
622
|
-
f"--owner
|
|
745
|
+
f"--owner 只在客户端过滤:服务端只支持按调用方身份过滤(--mine),"
|
|
623
746
|
f"本次在 {len(records)} 条结果里筛 owner == {wanted_owner}"
|
|
624
747
|
)
|
|
625
748
|
records = [record for record in records if str(record.get("owner") or "") == wanted_owner]
|
|
626
749
|
payload["owner_filter"] = wanted_owner
|
|
750
|
+
if mine:
|
|
751
|
+
payload["owned_by_me"] = True
|
|
627
752
|
|
|
628
753
|
rows = [_dataset_row(record) for record in records]
|
|
629
754
|
payload["datasets"] = [_legacy_dataset_record(record) for record in records]
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ap-client"
|
|
7
|
-
version = "0.2.2.
|
|
7
|
+
version = "0.2.2.dev2"
|
|
8
8
|
description = "Agent Platform API Client & CLI"
|
|
9
9
|
readme = { text = "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.", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|