ap-client 0.5.1.dev5__tar.gz → 0.5.1.dev6__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.5.1.dev5 → ap_client-0.5.1.dev6}/PKG-INFO +4 -3
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/akpool_commands.py +3 -1
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/api.py +53 -16
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/cli.py +93 -10
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/dataset_commands.py +14 -40
- ap_client-0.5.1.dev6/ap_client/exporter.py +729 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/instance_commands.py +2 -15
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/irepo_sdk.py +2 -574
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/pyproject.toml +4 -3
- ap_client-0.5.1.dev5/ap_client/dataset_register.py +0 -320
- ap_client-0.5.1.dev5/ap_client/exporter.py +0 -934
- ap_client-0.5.1.dev5/ap_client/manifest_view.py +0 -103
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/__init__.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/config.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/fs_commands.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/managed_ak.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/profile_commands.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/split_publish.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/tbb.py +0 -0
- {ap_client-0.5.1.dev5 → ap_client-0.5.1.dev6}/ap_client/waiter.py +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.5.1.
|
|
3
|
+
Version: 0.5.1.dev6
|
|
4
4
|
Summary: Agent Platform API Client & CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: irepo-artifact==0.2.2
|
|
6
7
|
Requires-Dist: pyyaml>=6.0
|
|
7
8
|
Requires-Dist: requests>=2.28.0
|
|
8
9
|
Requires-Dist: rich>=13.0.0
|
|
9
10
|
Requires-Dist: typer>=0.9.0
|
|
10
11
|
Requires-Dist: websockets>=13.0
|
|
11
12
|
Provides-Extra: all
|
|
12
|
-
Requires-Dist: instance-repo[oss]
|
|
13
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.1.4; extra == 'all'
|
|
13
14
|
Provides-Extra: dataset
|
|
14
|
-
Requires-Dist: instance-repo[oss]
|
|
15
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.1.4; extra == 'dataset'
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
|
|
17
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.
|
|
@@ -63,7 +63,9 @@ def _fmt(output_format: Optional[str]) -> str:
|
|
|
63
63
|
def _check_time_range(value: str) -> str:
|
|
64
64
|
normalized = str(value or "").strip().lower()
|
|
65
65
|
if not _TIME_RANGE_RE.fullmatch(normalized):
|
|
66
|
-
raise typer.BadParameter(
|
|
66
|
+
raise typer.BadParameter(
|
|
67
|
+
f"--time-range must be a duration like 30s/5m/1h, got {value!r}"
|
|
68
|
+
)
|
|
67
69
|
return normalized
|
|
68
70
|
|
|
69
71
|
|
|
@@ -123,6 +123,14 @@ _UNSET: Any = object()
|
|
|
123
123
|
# when aggregation runs later.
|
|
124
124
|
EXTRA_AGG_OPS = ("avg", "sum")
|
|
125
125
|
|
|
126
|
+
# Others 占位:apiserver 词表不返回,CLI 展示与 get_meta_model("Others") 兜底用。
|
|
127
|
+
_OTHERS_MODEL = {
|
|
128
|
+
"name": "Others",
|
|
129
|
+
"series_name": "",
|
|
130
|
+
"source_id": "",
|
|
131
|
+
"series_id": "",
|
|
132
|
+
}
|
|
133
|
+
|
|
126
134
|
|
|
127
135
|
def _merge_meta_tags(
|
|
128
136
|
tags: Optional[list],
|
|
@@ -968,19 +976,18 @@ class APIClient:
|
|
|
968
976
|
# `dataset versions/instances`、`job create --dataset`)。
|
|
969
977
|
|
|
970
978
|
def list_meta_models(self) -> dict:
|
|
971
|
-
"""
|
|
972
|
-
|
|
973
|
-
return {
|
|
974
|
-
"items": [
|
|
975
|
-
{**item, "name": item["value"]}
|
|
976
|
-
for item in items
|
|
977
|
-
if item.get("value") and item.get("is_enabled", True)
|
|
978
|
-
]
|
|
979
|
-
}
|
|
979
|
+
"""模型词表(apiserver 权威源 GET /apis/v1/meta-models,返回 {items:[...]})。"""
|
|
980
|
+
return self._get_central("/meta-models")
|
|
980
981
|
|
|
981
982
|
def get_meta_model(self, name: str) -> dict:
|
|
982
|
-
"""
|
|
983
|
-
|
|
983
|
+
"""单个模型详情(按 name 或 source_id 从 apiserver list 过滤;Others 返回占位)。
|
|
984
|
+
|
|
985
|
+
apiserver 没有单模型详情接口,客户端从 list 结果过滤;找不到 raise
|
|
986
|
+
APIError 404,与原 cluster AP /meta/models/{name} 行为一致。
|
|
987
|
+
"""
|
|
988
|
+
if name == "Others":
|
|
989
|
+
return dict(_OTHERS_MODEL)
|
|
990
|
+
result = self._get_central("/meta-models")
|
|
984
991
|
for item in result.get("items") or []:
|
|
985
992
|
if item.get("name") == name or item.get("source_id") == name:
|
|
986
993
|
return item
|
|
@@ -1046,13 +1053,43 @@ class APIClient:
|
|
|
1046
1053
|
return result
|
|
1047
1054
|
|
|
1048
1055
|
def list_meta_job_types(self) -> dict:
|
|
1049
|
-
"""
|
|
1050
|
-
|
|
1056
|
+
"""任务类型词表(目录驱动:GET /apis/v1/meta-tags?key=meta-job-type)。
|
|
1057
|
+
|
|
1058
|
+
仅取目录中 ``is_enabled`` 的值(缺失按启用解析)+ 末尾 ``Others``
|
|
1059
|
+
兜底行;``default`` 为 ``Eval-Eval``(在目录中时,否则空串)。
|
|
1060
|
+
目录请求失败或为空 → 回退本地常量(Eval-Eval + Others,fail-open),
|
|
1061
|
+
并在 stderr 打一行告警,词表降级可见但不阻塞命令。
|
|
1062
|
+
"""
|
|
1063
|
+
error: Exception | None = None
|
|
1064
|
+
try:
|
|
1065
|
+
items = self.get_meta_tags("meta-job-type").get("items") or []
|
|
1066
|
+
except Exception as exc: # fail-open:目录不可用不阻塞词表命令
|
|
1067
|
+
error = exc
|
|
1068
|
+
items = []
|
|
1069
|
+
if items:
|
|
1070
|
+
options = []
|
|
1071
|
+
for item in items:
|
|
1072
|
+
value = str(item.get("value") or "").strip()
|
|
1073
|
+
if value and item.get("is_enabled", True):
|
|
1074
|
+
options.append({"value": value})
|
|
1075
|
+
if options:
|
|
1076
|
+
default = "Eval-Eval" if any(o["value"] == "Eval-Eval" for o in options) else ""
|
|
1077
|
+
return {
|
|
1078
|
+
"meta_job_type": {
|
|
1079
|
+
"default": default,
|
|
1080
|
+
"options": [*options, {"value": "Others"}],
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
_write_stderr(
|
|
1084
|
+
[
|
|
1085
|
+
"warning: meta-job-type catalog unavailable or empty, "
|
|
1086
|
+
f"falling back to local constants (reason={error or 'empty catalog'})"
|
|
1087
|
+
]
|
|
1088
|
+
)
|
|
1051
1089
|
return {
|
|
1052
1090
|
"meta_job_type": {
|
|
1053
|
-
"
|
|
1054
|
-
|
|
1055
|
-
],
|
|
1091
|
+
"default": "Eval-Eval",
|
|
1092
|
+
"options": [{"value": "Eval-Eval"}, {"value": "Others"}],
|
|
1056
1093
|
}
|
|
1057
1094
|
}
|
|
1058
1095
|
|
|
@@ -15,6 +15,7 @@ import typer
|
|
|
15
15
|
from ap_client import __version__, get_client, get_config, tbb
|
|
16
16
|
from ap_client.akpool_commands import register as _register_akpool_commands
|
|
17
17
|
from ap_client.api import (
|
|
18
|
+
_OTHERS_MODEL,
|
|
18
19
|
APIError,
|
|
19
20
|
PaginationProtocolError,
|
|
20
21
|
_is_sensitive_key,
|
|
@@ -25,7 +26,7 @@ from ap_client.api import (
|
|
|
25
26
|
)
|
|
26
27
|
from ap_client.config import ENV_VAR_SPECS, ConfigurationError, _parse_bool, normalize_output_format
|
|
27
28
|
from ap_client.dataset_commands import register as _register_dataset_commands
|
|
28
|
-
from ap_client.exporter import export_group, export_job
|
|
29
|
+
from ap_client.exporter import export_group, export_job, export_jobs
|
|
29
30
|
from ap_client.fs_commands import register as _register_fs_commands
|
|
30
31
|
from ap_client.instance_commands import register as _register_instance_commands
|
|
31
32
|
from ap_client.managed_ak import (
|
|
@@ -2688,22 +2689,23 @@ def meta_model_list(
|
|
|
2688
2689
|
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
2689
2690
|
),
|
|
2690
2691
|
):
|
|
2691
|
-
"""List
|
|
2692
|
+
"""List meta model options (apiserver data + Others as the last row)."""
|
|
2692
2693
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
2693
2694
|
result = get_client().list_meta_models()
|
|
2694
2695
|
items = result.get("items") or []
|
|
2695
|
-
options = items
|
|
2696
|
+
options = [*items, dict(_OTHERS_MODEL)] # 末尾固定追加 Others 兜底手动输入
|
|
2696
2697
|
if output_format in ("plain", "table"):
|
|
2697
2698
|
rows = [
|
|
2698
2699
|
{
|
|
2699
2700
|
"name": m.get("name") or "-",
|
|
2700
|
-
"
|
|
2701
|
+
"series": m.get("series_name") or "-",
|
|
2701
2702
|
}
|
|
2702
2703
|
for m in options
|
|
2703
2704
|
]
|
|
2704
|
-
_print_plain_table(rows, [("name", "name"), ("
|
|
2705
|
+
_print_plain_table(rows, [("name", "name"), ("series", "series")])
|
|
2705
2706
|
else:
|
|
2706
|
-
#
|
|
2707
|
+
# json/yaml 包装成 {meta_model:{options}}(原 cluster AP 格式;
|
|
2708
|
+
# default 已随 meta-models default 逻辑移除而下线)
|
|
2707
2709
|
_print_formatted({"meta_model": {"options": options}}, output_format)
|
|
2708
2710
|
|
|
2709
2711
|
|
|
@@ -2794,20 +2796,21 @@ def meta_job_type_list(
|
|
|
2794
2796
|
help="Output format: plain/table/json/yaml (default: AP_FORMAT or command default)",
|
|
2795
2797
|
),
|
|
2796
2798
|
):
|
|
2797
|
-
"""List
|
|
2799
|
+
"""List meta job type options (catalog-driven; falls back to Eval-Eval/Others)."""
|
|
2798
2800
|
output_format = _normalize_output_format(output_format, keep_table=True)
|
|
2799
2801
|
result = get_client().list_meta_job_types()
|
|
2800
2802
|
info = result.get("meta_job_type") or {}
|
|
2801
2803
|
options = info.get("options") or []
|
|
2804
|
+
default = info.get("default") or ""
|
|
2802
2805
|
if output_format in ("plain", "table"):
|
|
2803
2806
|
rows = [
|
|
2804
2807
|
{
|
|
2805
2808
|
"value": o.get("value") or "-",
|
|
2806
|
-
"
|
|
2809
|
+
"default": "*" if o.get("value") and o.get("value") == default else "",
|
|
2807
2810
|
}
|
|
2808
2811
|
for o in options
|
|
2809
2812
|
]
|
|
2810
|
-
_print_plain_table(rows, [("value", "value"), ("
|
|
2813
|
+
_print_plain_table(rows, [("value", "value"), ("default", "default")])
|
|
2811
2814
|
else:
|
|
2812
2815
|
_print_formatted(result, output_format)
|
|
2813
2816
|
|
|
@@ -4876,10 +4879,47 @@ def job_artifacts(
|
|
|
4876
4879
|
_print_formatted(results, output_format)
|
|
4877
4880
|
|
|
4878
4881
|
|
|
4882
|
+
def _validate_export_timeout(value: float) -> float:
|
|
4883
|
+
import math
|
|
4884
|
+
|
|
4885
|
+
if not math.isfinite(value) or value <= 0:
|
|
4886
|
+
raise typer.BadParameter("Timeout must be a finite number greater than zero")
|
|
4887
|
+
return value
|
|
4888
|
+
|
|
4889
|
+
|
|
4890
|
+
def _export_channel_reporter(emit):
|
|
4891
|
+
"""Report completed Jobs, including callbacks from Group worker threads."""
|
|
4892
|
+
from threading import Lock
|
|
4893
|
+
|
|
4894
|
+
from rich.text import Text
|
|
4895
|
+
|
|
4896
|
+
counts = {"oss": 0, "proxy": 0}
|
|
4897
|
+
lock = Lock()
|
|
4898
|
+
|
|
4899
|
+
def report(job_id, status, channel):
|
|
4900
|
+
with lock:
|
|
4901
|
+
suffix = ""
|
|
4902
|
+
if status == "downloaded" and channel in counts:
|
|
4903
|
+
counts[channel] += 1
|
|
4904
|
+
suffix = f" channel={channel}"
|
|
4905
|
+
emit(Text(f"{job_id} {status}{suffix}"))
|
|
4906
|
+
|
|
4907
|
+
return report, counts
|
|
4908
|
+
|
|
4909
|
+
|
|
4879
4910
|
@job_app.command("export")
|
|
4880
4911
|
def job_export(
|
|
4881
|
-
job_id: str = typer.Argument(..., help="Job ID"),
|
|
4912
|
+
job_id: str = typer.Argument(..., help="Job ID or comma-separated Job IDs"),
|
|
4882
4913
|
output: Optional[Path] = typer.Option(None, "--output", "-o", help="Export directory"),
|
|
4914
|
+
workers: int = typer.Option(
|
|
4915
|
+
1, "--workers", "-w", min=1, help="Number of concurrent export workers"
|
|
4916
|
+
),
|
|
4917
|
+
timeout: float = typer.Option(
|
|
4918
|
+
300,
|
|
4919
|
+
"--timeout",
|
|
4920
|
+
callback=_validate_export_timeout,
|
|
4921
|
+
help="SDK request timeout in seconds, not the total export duration",
|
|
4922
|
+
),
|
|
4883
4923
|
logs: bool = typer.Option(False, "--logs", help="Include container logs"),
|
|
4884
4924
|
events: bool = typer.Option(False, "--events", help="Include events"),
|
|
4885
4925
|
no_extract_artifacts: bool = typer.Option(
|
|
@@ -4889,17 +4929,50 @@ def job_export(
|
|
|
4889
4929
|
),
|
|
4890
4930
|
):
|
|
4891
4931
|
"""Export job artifacts to a local directory. Use --logs/--events to include logs and events."""
|
|
4932
|
+
ids = list(dict.fromkeys(value.strip() for value in job_id.split(",")))
|
|
4933
|
+
if not all(ids):
|
|
4934
|
+
raise typer.BadParameter("Job IDs must not be empty", param_hint="job_id")
|
|
4892
4935
|
client = get_client()
|
|
4936
|
+
if len(ids) > 1:
|
|
4937
|
+
report, channels = _export_channel_reporter(print)
|
|
4938
|
+
dest, summary = export_jobs(
|
|
4939
|
+
client,
|
|
4940
|
+
ids,
|
|
4941
|
+
output,
|
|
4942
|
+
workers=workers,
|
|
4943
|
+
result_callback=report,
|
|
4944
|
+
timeout=timeout,
|
|
4945
|
+
include_logs=logs,
|
|
4946
|
+
include_events=events,
|
|
4947
|
+
extract_artifacts=not no_extract_artifacts,
|
|
4948
|
+
)
|
|
4949
|
+
print(
|
|
4950
|
+
f"Jobs exported: downloaded={summary.downloaded} "
|
|
4951
|
+
f"skipped={summary.skipped} failed={len(summary.failures)}"
|
|
4952
|
+
)
|
|
4953
|
+
print(f"channels: oss={channels['oss']} proxy={channels['proxy']}")
|
|
4954
|
+
print(f" path: {dest}")
|
|
4955
|
+
for failed_id, error in summary.failures.items():
|
|
4956
|
+
print(f"[red]Failed job {failed_id}:[/] {error}")
|
|
4957
|
+
if summary.failures:
|
|
4958
|
+
raise typer.Exit(code=1)
|
|
4959
|
+
return
|
|
4960
|
+
job_id = ids[0]
|
|
4961
|
+
result = {}
|
|
4893
4962
|
dest = export_job(
|
|
4894
4963
|
client,
|
|
4895
4964
|
job_id,
|
|
4896
4965
|
output,
|
|
4966
|
+
result_callback=lambda jid, status, channel: result.update(status=status, channel=channel),
|
|
4967
|
+
timeout=timeout,
|
|
4897
4968
|
include_logs=logs,
|
|
4898
4969
|
include_events=events,
|
|
4899
4970
|
extract_artifacts=not no_extract_artifacts,
|
|
4900
4971
|
)
|
|
4901
4972
|
print(f"[green]Job exported:[/] {job_id}")
|
|
4902
4973
|
print(f" path: {dest}")
|
|
4974
|
+
if result.get("channel") in {"oss", "proxy"} and result.get("status") == "downloaded":
|
|
4975
|
+
print(f" channel: {result['channel']}")
|
|
4903
4976
|
|
|
4904
4977
|
|
|
4905
4978
|
@job_app.command("wait")
|
|
@@ -6374,6 +6447,12 @@ def group_export(
|
|
|
6374
6447
|
workers: int = typer.Option(
|
|
6375
6448
|
4, "--workers", "-w", min=1, help="Number of concurrent export workers"
|
|
6376
6449
|
),
|
|
6450
|
+
timeout: float = typer.Option(
|
|
6451
|
+
300,
|
|
6452
|
+
"--timeout",
|
|
6453
|
+
callback=_validate_export_timeout,
|
|
6454
|
+
help="SDK request timeout in seconds, not the total export duration",
|
|
6455
|
+
),
|
|
6377
6456
|
logs: bool = typer.Option(False, "--logs", help="Include container logs"),
|
|
6378
6457
|
events: bool = typer.Option(False, "--events", help="Include events"),
|
|
6379
6458
|
no_extract_artifacts: bool = typer.Option(
|
|
@@ -6434,12 +6513,15 @@ def group_export(
|
|
|
6434
6513
|
current_stage=current_stage,
|
|
6435
6514
|
)
|
|
6436
6515
|
|
|
6516
|
+
report, channels = _export_channel_reporter(progress.console.print)
|
|
6437
6517
|
dest, summary = export_group(
|
|
6438
6518
|
client,
|
|
6439
6519
|
group_id,
|
|
6440
6520
|
output,
|
|
6441
6521
|
progress_callback=_on_progress,
|
|
6442
6522
|
workers=workers,
|
|
6523
|
+
result_callback=report,
|
|
6524
|
+
timeout=timeout,
|
|
6443
6525
|
include_logs=logs,
|
|
6444
6526
|
include_events=events,
|
|
6445
6527
|
extract_artifacts=not no_extract_artifacts,
|
|
@@ -6452,6 +6534,7 @@ def group_export(
|
|
|
6452
6534
|
f" [green]{summary.downloaded}[/] jobs downloaded, "
|
|
6453
6535
|
f"[yellow]{summary.skipped}[/] jobs skipped (no artifact)"
|
|
6454
6536
|
)
|
|
6537
|
+
print(f"channels: oss={channels['oss']} proxy={channels['proxy']}")
|
|
6455
6538
|
if summary.skipped_job_names:
|
|
6456
6539
|
print(f" Skipped: {', '.join(summary.skipped_job_names)}")
|
|
6457
6540
|
|
|
@@ -50,15 +50,18 @@ def register(dataset_app: typer.Typer) -> None:
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def _register_dataset_commands(dataset_app: typer.Typer) -> None:
|
|
53
|
-
"""挂载 dataset
|
|
54
|
-
from .dataset_register import dataset_register
|
|
53
|
+
"""挂载 dataset 级命令(``list`` / ``get`` / ``create`` / ``update`` / ``claim``)。
|
|
55
54
|
|
|
55
|
+
实现方式:把命令写成本模块的模块级函数,再在这里用
|
|
56
|
+
``dataset_app.command("<name>")(<func>)`` 挂上去。``list`` 需要**替换**
|
|
57
|
+
``cli.py`` 里现存的 ossdata 版本(设计 §4.1),挂载时用同名 ``"list"`` 覆盖,
|
|
58
|
+
并同步删掉 ``cli.py`` 中的旧实现。
|
|
59
|
+
"""
|
|
56
60
|
dataset_app.command("list")(dataset_list)
|
|
57
61
|
dataset_app.command("get")(dataset_get)
|
|
58
62
|
dataset_app.command("create")(dataset_create)
|
|
59
63
|
dataset_app.command("update")(dataset_update)
|
|
60
64
|
dataset_app.command("claim")(dataset_claim)
|
|
61
|
-
dataset_app.command("register")(dataset_register)
|
|
62
65
|
|
|
63
66
|
|
|
64
67
|
# ---------------------------------------------------------------------------
|
|
@@ -83,7 +86,6 @@ _VERSION_COLUMNS: tuple[tuple[str, str], ...] = (
|
|
|
83
86
|
("splits", "SPLITS"),
|
|
84
87
|
("instances", "INSTANCES"),
|
|
85
88
|
("updated_at", "UPDATED_AT"),
|
|
86
|
-
("registered", "REGISTERED"),
|
|
87
89
|
)
|
|
88
90
|
|
|
89
91
|
#: 上游 §8.2 的默认列(设计 §5)。
|
|
@@ -92,7 +94,6 @@ _SPLIT_COLUMNS: tuple[tuple[str, str], ...] = (
|
|
|
92
94
|
("run_type", "RUN_TYPE"),
|
|
93
95
|
("instances", "INSTANCES"),
|
|
94
96
|
("updated_at", "UPDATED_AT"),
|
|
95
|
-
("registered", "REGISTERED"),
|
|
96
97
|
)
|
|
97
98
|
|
|
98
99
|
_ACCESS_COLUMNS: tuple[tuple[str, str], ...] = (
|
|
@@ -397,7 +398,6 @@ def _model_dict(record: Any) -> dict:
|
|
|
397
398
|
``{}`` / 缺省都不会出现在响应里),而 SDK 模型会把它们补成默认值;留着这些默认值会让
|
|
398
399
|
JSON 凭空多出字段(迁移前是原样透出服务端响应,契约必须一致)。
|
|
399
400
|
|
|
400
|
-
``registered`` 是显式注册状态,必须保留 ``false``。
|
|
401
401
|
只清洗**顶层**:嵌套结构(如 version 详情里的 ``splits[].is_default``)按模型原样输出,
|
|
402
402
|
服务端在那里确实是显式返回 ``false``。
|
|
403
403
|
"""
|
|
@@ -412,24 +412,13 @@ def _model_dict(record: Any) -> dict:
|
|
|
412
412
|
return {}
|
|
413
413
|
if not isinstance(payload, dict):
|
|
414
414
|
return {}
|
|
415
|
-
registered = getattr(record, "registered", None)
|
|
416
|
-
if isinstance(registered, bool):
|
|
417
|
-
payload = {**payload, "registered": registered}
|
|
418
415
|
return {
|
|
419
416
|
key: value
|
|
420
417
|
for key, value in payload.items()
|
|
421
|
-
if value is
|
|
422
|
-
and (key == "registered" or not (value is False or value == "" or value == [] or value == {}))
|
|
418
|
+
if not (value is None or value is False or value == "" or value == [] or value == {})
|
|
423
419
|
}
|
|
424
420
|
|
|
425
421
|
|
|
426
|
-
def _registered_dict(record: Any) -> dict:
|
|
427
|
-
"""Preserve SDK registration state; older metadata-only SDK reads imply true."""
|
|
428
|
-
payload = _model_dict(record)
|
|
429
|
-
payload.setdefault("registered", True)
|
|
430
|
-
return payload
|
|
431
|
-
|
|
432
|
-
|
|
433
422
|
def _api_biz_code(exc: BaseException) -> Optional[int]:
|
|
434
423
|
"""从 ``APIError`` 里取 apiserver 业务码(设计 §5.1),不 import 数值异常类。
|
|
435
424
|
|
|
@@ -825,7 +814,6 @@ def _version_row(record: dict) -> dict:
|
|
|
825
814
|
"splits": splits,
|
|
826
815
|
"instances": record.get("instance_count", ""),
|
|
827
816
|
"updated_at": record.get("updated_at") or "",
|
|
828
|
-
"registered": record.get("registered", True),
|
|
829
817
|
}
|
|
830
818
|
|
|
831
819
|
|
|
@@ -845,7 +833,6 @@ def _split_row(record: dict, detail: dict) -> dict:
|
|
|
845
833
|
"run_type": record.get("run_type") or detail.get("run_type") or "",
|
|
846
834
|
"instances": "" if instances is None else instances,
|
|
847
835
|
"updated_at": record.get("updated_at") or detail.get("updated_at") or "",
|
|
848
|
-
"registered": record.get("registered", detail.get("registered", True)),
|
|
849
836
|
}
|
|
850
837
|
|
|
851
838
|
|
|
@@ -1157,22 +1144,11 @@ def dataset_get(
|
|
|
1157
1144
|
try:
|
|
1158
1145
|
detail = repo.datasets.get_detail(dataset, **_env_kwargs(env))
|
|
1159
1146
|
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
detail = None
|
|
1166
|
-
if detail is None:
|
|
1167
|
-
l1, _, l2 = dataset.partition("/")
|
|
1168
|
-
rendered = {
|
|
1169
|
-
"dataset_name": dataset,
|
|
1170
|
-
"dataset_l1": l1,
|
|
1171
|
-
"dataset_l2": l2,
|
|
1172
|
-
"registered": False,
|
|
1173
|
-
}
|
|
1174
|
-
else:
|
|
1175
|
-
rendered = _registered_dict(detail)
|
|
1147
|
+
raise _fail(
|
|
1148
|
+
f"Failed to query dataset {dataset}: {_reason(exc)}",
|
|
1149
|
+
f"Check the dataset name: ap dataset list --query {dataset}",
|
|
1150
|
+
) from exc
|
|
1151
|
+
rendered = _model_dict(detail)
|
|
1176
1152
|
_emit_detail(rendered, output_format, order=_DATASET_DETAIL_ORDER, always=("status",))
|
|
1177
1153
|
if output_format not in ("json", "yaml"):
|
|
1178
1154
|
_print_dataset_environments(repo, dataset, rendered)
|
|
@@ -1666,7 +1642,7 @@ def dataset_version_list(
|
|
|
1666
1642
|
f"Check the dataset name: ap dataset list --query {dataset}",
|
|
1667
1643
|
) from exc
|
|
1668
1644
|
|
|
1669
|
-
records = [
|
|
1645
|
+
records = [_model_dict(record) for record in (getattr(result, "items", None) or [])]
|
|
1670
1646
|
rows = [_version_row(record) for record in records]
|
|
1671
1647
|
_emit(
|
|
1672
1648
|
{"dataset": dataset, "versions": records, "pagination": _pagination_envelope(result)},
|
|
@@ -1687,7 +1663,7 @@ def dataset_version_get(
|
|
|
1687
1663
|
output_format = _fmt(output_format)
|
|
1688
1664
|
resolved = _version_value(version)
|
|
1689
1665
|
detail = _fetch_version(dataset, resolved, stage="query", environment=environment)
|
|
1690
|
-
_emit_detail(
|
|
1666
|
+
_emit_detail(detail, output_format, order=_VERSION_DETAIL_ORDER)
|
|
1691
1667
|
|
|
1692
1668
|
|
|
1693
1669
|
@dataset_version_app.command("create")
|
|
@@ -1834,7 +1810,6 @@ def dataset_split_get(
|
|
|
1834
1810
|
payload = dict(record)
|
|
1835
1811
|
payload.setdefault("dataset", dataset)
|
|
1836
1812
|
payload.setdefault("version", resolved_version)
|
|
1837
|
-
payload.setdefault("registered", detail.get("registered", True))
|
|
1838
1813
|
_emit_detail(payload, output_format)
|
|
1839
1814
|
return
|
|
1840
1815
|
known = ", ".join(_split_names(detail)) or "<none>"
|
|
@@ -1844,7 +1819,6 @@ def dataset_split_get(
|
|
|
1844
1819
|
payload = dict(record)
|
|
1845
1820
|
payload.setdefault("dataset", dataset)
|
|
1846
1821
|
payload.setdefault("version", "")
|
|
1847
|
-
payload.setdefault("registered", True)
|
|
1848
1822
|
_emit_detail(payload, output_format)
|
|
1849
1823
|
return
|
|
1850
1824
|
known = (
|