ap-client 0.7.0.dev3__tar.gz → 0.7.0.dev5__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.7.0.dev3 → ap_client-0.7.0.dev5}/PKG-INFO +4 -4
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/cli.py +17 -5
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/dataset_commands.py +205 -117
- ap_client-0.7.0.dev5/ap_client/dataset_name.py +104 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/dataset_register.py +76 -7
- ap_client-0.7.0.dev5/ap_client/dfs_commands.py +705 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/fs_commands.py +199 -20
- ap_client-0.7.0.dev5/ap_client/fs_storage.py +146 -0
- ap_client-0.7.0.dev5/ap_client/fs_transfer.py +329 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/instance_commands.py +519 -52
- ap_client-0.7.0.dev5/ap_client/instance_options.py +89 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/irepo_sdk.py +129 -4
- ap_client-0.7.0.dev5/ap_client/ossutil_hash.py +329 -0
- ap_client-0.7.0.dev5/ap_client/ossutil_io.py +1271 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/repo_read_guard.py +6 -2
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/split_publish.py +1 -1
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/pyproject.toml +5 -12
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/__init__.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/akpool_commands.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/api.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/config.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/exporter.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/managed_ak.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/manifest_view.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/profile_commands.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/share_api.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/tbb.py +0 -0
- {ap_client-0.7.0.dev3 → ap_client-0.7.0.dev5}/ap_client/waiter.py +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ap-client
|
|
3
|
-
Version: 0.7.0.
|
|
3
|
+
Version: 0.7.0.dev5
|
|
4
4
|
Summary: Agent Platform API Client & CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
|
-
Requires-Dist: irepo-artifact==0.2.4
|
|
6
|
+
Requires-Dist: irepo-artifact==0.2.4
|
|
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.
|
|
13
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.4.0; extra == 'all'
|
|
14
14
|
Provides-Extra: dataset
|
|
15
|
-
Requires-Dist: instance-repo[oss]<2.0.0,>=1.
|
|
15
|
+
Requires-Dist: instance-repo[oss]<2.0.0,>=1.4.0; 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.
|
|
@@ -23,6 +23,7 @@ from ap_client.api import (
|
|
|
23
23
|
)
|
|
24
24
|
from ap_client.config import ENV_VAR_SPECS, ConfigurationError, _parse_bool, normalize_output_format
|
|
25
25
|
from ap_client.dataset_commands import register as _register_dataset_commands
|
|
26
|
+
from ap_client.dfs_commands import register as _register_dfs_commands
|
|
26
27
|
from ap_client.exporter import export_group, export_job, export_jobs
|
|
27
28
|
from ap_client.fs_commands import register as _register_fs_commands
|
|
28
29
|
from ap_client.instance_commands import register as _register_instance_commands
|
|
@@ -165,6 +166,7 @@ app.add_typer(checkpoint_app, name="checkpoint")
|
|
|
165
166
|
# ap dataset version/split/access 嵌套在 dataset 下;instance/fs 是一等资源。
|
|
166
167
|
_register_dataset_commands(dataset_app)
|
|
167
168
|
_register_instance_commands(app)
|
|
169
|
+
_register_dfs_commands(app)
|
|
168
170
|
_register_fs_commands(app)
|
|
169
171
|
_register_akpool_commands(app)
|
|
170
172
|
|
|
@@ -3001,22 +3003,22 @@ def job_create(
|
|
|
3001
3003
|
meta_model: Optional[str] = typer.Option(
|
|
3002
3004
|
None,
|
|
3003
3005
|
"--meta-model",
|
|
3004
|
-
help="
|
|
3006
|
+
help="Target model or training project this job serves; not the called model or Judge. Values: ap meta-tag get meta-model --format json",
|
|
3005
3007
|
),
|
|
3006
3008
|
meta_job_type: Optional[str] = typer.Option(
|
|
3007
3009
|
None,
|
|
3008
3010
|
"--meta-job-type",
|
|
3009
|
-
help="
|
|
3011
|
+
help="Business purpose of this job. Values: ap meta-tag get meta-job-type --format json",
|
|
3010
3012
|
),
|
|
3011
3013
|
meta_teacher_direction: Optional[str] = typer.Option(
|
|
3012
3014
|
None,
|
|
3013
3015
|
"--meta-teacher-direction",
|
|
3014
|
-
help="
|
|
3016
|
+
help="Teacher direction this job serves; verify any dataset-derived value. Values: ap meta-tag get meta-teacher-direction --format json",
|
|
3015
3017
|
),
|
|
3016
3018
|
meta_train_stage: Optional[str] = typer.Option(
|
|
3017
3019
|
None,
|
|
3018
3020
|
"--meta-train-stage",
|
|
3019
|
-
help="
|
|
3021
|
+
help="Training stage this job serves; verify any dataset-derived value. Values: ap meta-tag get meta-train-stage --format json",
|
|
3020
3022
|
),
|
|
3021
3023
|
overrides: Optional[str] = typer.Option(None, "--overrides", help="Override config (JSON)"),
|
|
3022
3024
|
concurrency: Optional[int] = typer.Option(
|
|
@@ -3188,7 +3190,7 @@ def job_create(
|
|
|
3188
3190
|
benchmark: Optional[str] = typer.Option(
|
|
3189
3191
|
None,
|
|
3190
3192
|
"--benchmark",
|
|
3191
|
-
help="
|
|
3193
|
+
help="Prefer the registered benchmark source ID for this run; name is also accepted. Find IDs: ap benchmark list --format json; inspect by name: ap benchmark get NAME --format json",
|
|
3192
3194
|
),
|
|
3193
3195
|
checkpoint_id: Optional[str] = typer.Option(
|
|
3194
3196
|
None,
|
|
@@ -3234,6 +3236,16 @@ def job_create(
|
|
|
3234
3236
|
):
|
|
3235
3237
|
"""Submit a job.
|
|
3236
3238
|
|
|
3239
|
+
For agents:
|
|
3240
|
+
\b
|
|
3241
|
+
Before submitting, resolve benchmark, target model, job purpose,
|
|
3242
|
+
training stage, and teacher direction using live catalogs and task context.
|
|
3243
|
+
Verify applicable inherited values: ap profile get ID --version N --format json;
|
|
3244
|
+
ap group get ID --format json. Job tags do not overwrite existing Group metadata.
|
|
3245
|
+
Do not infer target model from serving/Judge models or training stage from evaluation.
|
|
3246
|
+
Ask the user for missing or conflicting business context before choosing values.
|
|
3247
|
+
All five options remain optional; existing validation still applies.
|
|
3248
|
+
|
|
3237
3249
|
Model configuration:
|
|
3238
3250
|
Supply template parameters through --params/-p; select a managed credential with --ak-id.
|
|
3239
3251
|
|
|
@@ -123,7 +123,10 @@ _NO_BENCHMARK = "none"
|
|
|
123
123
|
_FORMAT_HELP = "Output format: plain/table/json/yaml (default: AP_FORMAT or command default)"
|
|
124
124
|
|
|
125
125
|
_METADATA_MODELS = ("version_first", "split_first")
|
|
126
|
-
|
|
126
|
+
# IR SDK >= 1.4.0.dev3 的业务入口固定 split_first:business_metadata_model 拒绝
|
|
127
|
+
# version_first(仅保留给低层 legacy 读)。带 version 的 split 查询在两种模型下
|
|
128
|
+
# 结果一致(实测),显式 split_first 与 SDK 归一值一致且新旧 SDK 均接受。
|
|
129
|
+
_VERSION_SPLIT_METADATA_MODEL = "split_first"
|
|
127
130
|
|
|
128
131
|
#: CLI **不替 SDK 决定元数据模型**:不下发 ``metadata_model``,由 SDK 按 profile 归一
|
|
129
132
|
#: (当前为 ``split_first``)。
|
|
@@ -691,6 +694,74 @@ def _unversioned_split(
|
|
|
691
694
|
return detail if detail is not None and not _version_value(detail.get("version")) else None
|
|
692
695
|
|
|
693
696
|
|
|
697
|
+
def _versioned_split(
|
|
698
|
+
dataset: str, version: str, split: str, *, environment: Optional[str] = None
|
|
699
|
+
) -> Optional[dict]:
|
|
700
|
+
"""取指定 version 里的单个 split 记录;不存在返回 ``None``(不当作错误)。
|
|
701
|
+
|
|
702
|
+
与 ``_unversioned_split`` 同源,走 recordsvc 的 ``splits.get``(SDK 自带
|
|
703
|
+
version_first 内嵌投影的 legacy 回退)。
|
|
704
|
+
"""
|
|
705
|
+
from .irepo_sdk import _reason
|
|
706
|
+
|
|
707
|
+
env = _environment(environment)
|
|
708
|
+
repo = _dataset_repo()
|
|
709
|
+
try:
|
|
710
|
+
record = repo.splits.get(
|
|
711
|
+
dataset, split, version=version, **_read_env_kwargs(repo, repo.splits, "get", env)
|
|
712
|
+
)
|
|
713
|
+
except Exception as exc: # noqa: BLE001
|
|
714
|
+
raise _fail(
|
|
715
|
+
f"dataset {dataset} version {version} split {split}: query failed: {_reason(exc)}",
|
|
716
|
+
f"Check the dataset name and permission: ap dataset get {dataset}",
|
|
717
|
+
) from exc
|
|
718
|
+
detail = _model_dict(record) if record is not None else None
|
|
719
|
+
return detail if detail is not None and _version_value(detail.get("version")) == version else None
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def _versioned_splits(
|
|
723
|
+
dataset: str, version: str, *, stage: str, environment: Optional[str] = None
|
|
724
|
+
) -> list[dict]:
|
|
725
|
+
"""读**指定 version**的 split——走 SDK ``repo.splits``(recordsvc)。
|
|
726
|
+
|
|
727
|
+
与 ``_unversioned_splits`` 同源:version 文档不内嵌 ``splits[]``(服务端只给
|
|
728
|
+
``split_count`` 聚合,split 记录由 recordsvc 的 ``/datasets/splits`` 承载),
|
|
729
|
+
SDK ``splits.list(version=...)`` 是唯一正确读法,且自带存量 version_first
|
|
730
|
+
内嵌投影的 legacy 回退。
|
|
731
|
+
"""
|
|
732
|
+
from .irepo_sdk import _reason
|
|
733
|
+
|
|
734
|
+
env = _environment(environment)
|
|
735
|
+
repo = _dataset_repo()
|
|
736
|
+
try:
|
|
737
|
+
records: list[dict] = []
|
|
738
|
+
page = 1
|
|
739
|
+
while True:
|
|
740
|
+
result = repo.splits.list(
|
|
741
|
+
dataset,
|
|
742
|
+
version=version,
|
|
743
|
+
page_size=_SPLIT_PAGE_SIZE,
|
|
744
|
+
**({"page": page} if page > 1 else {}),
|
|
745
|
+
**_read_env_kwargs(repo, repo.splits, "list", env),
|
|
746
|
+
)
|
|
747
|
+
items = getattr(result, "items", None) or []
|
|
748
|
+
for item in items:
|
|
749
|
+
record = _model_dict(item)
|
|
750
|
+
# 与 ``_versioned_split`` 同口径:只收 version 匹配的记录(服务端
|
|
751
|
+
# exact scope 保证;legacy 投影与 OSS 发现也会带 version)。
|
|
752
|
+
if _version_value(record.get("version")) == version:
|
|
753
|
+
records.append(record)
|
|
754
|
+
if not items or getattr(result, "has_more", False) is not True:
|
|
755
|
+
break
|
|
756
|
+
page += 1
|
|
757
|
+
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
758
|
+
raise _fail(
|
|
759
|
+
f"dataset {dataset} version {version}: {stage} failed: {_reason(exc)}",
|
|
760
|
+
f"Check the version name: ap dataset version get {version} --dataset {dataset}",
|
|
761
|
+
) from exc
|
|
762
|
+
return records
|
|
763
|
+
|
|
764
|
+
|
|
694
765
|
def _split_records(detail: dict) -> list[dict]:
|
|
695
766
|
"""version 文档内嵌的 ``splits[]``(设计 §4.3:split 没有独立资源)。"""
|
|
696
767
|
splits = detail.get("splits")
|
|
@@ -724,12 +795,12 @@ def _split_names(detail: dict) -> list[str]:
|
|
|
724
795
|
return names
|
|
725
796
|
|
|
726
797
|
|
|
727
|
-
def _dataset_repo() -> "Repo":
|
|
798
|
+
def _dataset_repo(*, request_timeout: float | None = None) -> "Repo":
|
|
728
799
|
"""dataset 写路径统一走 InstanceRepo SDK;未装 ``dataset`` extra 时退出 2。"""
|
|
729
800
|
from .irepo_sdk import SdkMissing, load_repo
|
|
730
801
|
|
|
731
802
|
try:
|
|
732
|
-
return load_repo()
|
|
803
|
+
return load_repo(request_timeout=request_timeout)
|
|
733
804
|
except SdkMissing as exc:
|
|
734
805
|
typer.echo(str(exc), err=True)
|
|
735
806
|
raise typer.Exit(2) from exc
|
|
@@ -1186,9 +1257,26 @@ def dataset_get(
|
|
|
1186
1257
|
"dataset_name": dataset,
|
|
1187
1258
|
"registered": False,
|
|
1188
1259
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1260
|
+
from .dataset_name import (
|
|
1261
|
+
DatasetNameError,
|
|
1262
|
+
DatasetNameUpgradeRequired,
|
|
1263
|
+
parse_dataset_name,
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
try:
|
|
1267
|
+
name = parse_dataset_name(dataset)
|
|
1268
|
+
except DatasetNameUpgradeRequired as exc:
|
|
1269
|
+
raise _fail(
|
|
1270
|
+
f"Failed to query dataset {dataset}: {exc}",
|
|
1271
|
+
"pip install 'ap-client[dataset]' --upgrade",
|
|
1272
|
+
exit_code=2,
|
|
1273
|
+
) from exc
|
|
1274
|
+
except DatasetNameError:
|
|
1275
|
+
# 非规范名:不凭空发明 level 字段(与历史行为一致)。
|
|
1276
|
+
pass
|
|
1277
|
+
else:
|
|
1278
|
+
rendered["dataset_l1"] = name.l1
|
|
1279
|
+
rendered["dataset_l2"] = name.l2
|
|
1192
1280
|
else:
|
|
1193
1281
|
rendered = _registered_dict(detail)
|
|
1194
1282
|
_emit_detail(rendered, output_format, order=_DATASET_DETAIL_ORDER, always=("status",))
|
|
@@ -1197,7 +1285,10 @@ def dataset_get(
|
|
|
1197
1285
|
|
|
1198
1286
|
|
|
1199
1287
|
def dataset_create(
|
|
1200
|
-
dataset: str = typer.Argument(
|
|
1288
|
+
dataset: str = typer.Argument(
|
|
1289
|
+
..., help="Dataset name: L1, L1/L2, or a deeper path "
|
|
1290
|
+
"(e.g. my-bench, alibaba/my-bench, or gdpval/synthetic/my-bench)"
|
|
1291
|
+
),
|
|
1201
1292
|
benchmark: str = typer.Option(..., "--benchmark", help="Benchmark name or source_id"),
|
|
1202
1293
|
description: Optional[str] = typer.Option(None, "--description", help="Dataset description"),
|
|
1203
1294
|
visibility: str = typer.Option(
|
|
@@ -1216,20 +1307,32 @@ def dataset_create(
|
|
|
1216
1307
|
|
|
1217
1308
|
from instance_repo.models import DatasetSeries
|
|
1218
1309
|
|
|
1310
|
+
from .dataset_name import (
|
|
1311
|
+
DatasetNameError,
|
|
1312
|
+
DatasetNameUpgradeRequired,
|
|
1313
|
+
parse_dataset_name,
|
|
1314
|
+
)
|
|
1219
1315
|
from .irepo_sdk import _reason
|
|
1220
1316
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1317
|
+
try:
|
|
1318
|
+
name = parse_dataset_name(dataset)
|
|
1319
|
+
except DatasetNameUpgradeRequired as exc:
|
|
1223
1320
|
raise _fail(
|
|
1224
|
-
f"Failed to create dataset {dataset}:
|
|
1225
|
-
"
|
|
1321
|
+
f"Failed to create dataset {dataset}: {exc}",
|
|
1322
|
+
"pip install 'ap-client[dataset]' --upgrade",
|
|
1323
|
+
exit_code=2,
|
|
1324
|
+
) from exc
|
|
1325
|
+
except DatasetNameError:
|
|
1326
|
+
raise _fail(
|
|
1327
|
+
f"Failed to create dataset {dataset}: the name must be one or more "
|
|
1328
|
+
"canonical path segments",
|
|
1329
|
+
"e.g. my-bench, your-team/my-bench, or gdpval/synthetic/my-bench",
|
|
1226
1330
|
exit_code=2,
|
|
1227
1331
|
)
|
|
1228
|
-
l1, l2 = parts
|
|
1229
1332
|
series = DatasetSeries(
|
|
1230
1333
|
dataset_name=dataset,
|
|
1231
|
-
dataset_l1=l1,
|
|
1232
|
-
dataset_l2=l2,
|
|
1334
|
+
dataset_l1=name.l1,
|
|
1335
|
+
dataset_l2=name.l2,
|
|
1233
1336
|
benchmark_id=benchmark_id,
|
|
1234
1337
|
visibility=resolved_visibility,
|
|
1235
1338
|
description=description or None,
|
|
@@ -1813,15 +1916,10 @@ def dataset_split_list(
|
|
|
1813
1916
|
resolved_run_type = _check_choice(run_type, _RUN_TYPES, "--run-type") if run_type else ""
|
|
1814
1917
|
|
|
1815
1918
|
if resolved_version:
|
|
1816
|
-
|
|
1817
|
-
dataset,
|
|
1818
|
-
resolved_version,
|
|
1819
|
-
stage="split query",
|
|
1820
|
-
environment=environment,
|
|
1821
|
-
metadata_model=_VERSION_SPLIT_METADATA_MODEL,
|
|
1919
|
+
records = _versioned_splits(
|
|
1920
|
+
dataset, resolved_version, stage="split query", environment=environment
|
|
1822
1921
|
)
|
|
1823
|
-
|
|
1824
|
-
rows = [_split_row(record, detail) for record in records]
|
|
1922
|
+
rows = [_split_row(record, {}) for record in records]
|
|
1825
1923
|
else:
|
|
1826
1924
|
records = _unversioned_splits(dataset, stage="split query", environment=environment)
|
|
1827
1925
|
rows = [_split_row(record, {}) for record in records]
|
|
@@ -1848,22 +1946,26 @@ def dataset_split_get(
|
|
|
1848
1946
|
resolved_version = _version_value(version)
|
|
1849
1947
|
|
|
1850
1948
|
if resolved_version:
|
|
1851
|
-
|
|
1852
|
-
dataset,
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1949
|
+
record = _versioned_split(
|
|
1950
|
+
dataset, resolved_version, split, environment=environment
|
|
1951
|
+
)
|
|
1952
|
+
if record is not None:
|
|
1953
|
+
payload = dict(record)
|
|
1954
|
+
payload.setdefault("dataset", dataset)
|
|
1955
|
+
payload.setdefault("version", resolved_version)
|
|
1956
|
+
payload.setdefault("registered", True)
|
|
1957
|
+
_emit_detail(payload, output_format)
|
|
1958
|
+
return
|
|
1959
|
+
known = (
|
|
1960
|
+
", ".join(
|
|
1961
|
+
_split_names_of(
|
|
1962
|
+
_versioned_splits(
|
|
1963
|
+
dataset, resolved_version, stage="split query", environment=environment
|
|
1964
|
+
)
|
|
1965
|
+
)
|
|
1966
|
+
)
|
|
1967
|
+
or "<none>"
|
|
1857
1968
|
)
|
|
1858
|
-
for record in _split_records(detail):
|
|
1859
|
-
if str(record.get("name") or "") == split:
|
|
1860
|
-
payload = dict(record)
|
|
1861
|
-
payload.setdefault("dataset", dataset)
|
|
1862
|
-
payload.setdefault("version", resolved_version)
|
|
1863
|
-
payload.setdefault("registered", detail.get("registered", True))
|
|
1864
|
-
_emit_detail(payload, output_format)
|
|
1865
|
-
return
|
|
1866
|
-
known = ", ".join(_split_names(detail)) or "<none>"
|
|
1867
1969
|
else:
|
|
1868
1970
|
record = _unversioned_split(dataset, split, environment=environment)
|
|
1869
1971
|
if record is not None:
|
|
@@ -1920,71 +2022,40 @@ def dataset_split_create(
|
|
|
1920
2022
|
exit_code=2,
|
|
1921
2023
|
)
|
|
1922
2024
|
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
)
|
|
1930
|
-
|
|
1931
|
-
existing = _split_names(detail)
|
|
1932
|
-
if split in existing:
|
|
2025
|
+
# 版本化创建走独立 Split API(POST /datasets/splits),与 split-first 服务端
|
|
2026
|
+
# 契约对齐——旧 Version PATCH(splits 全量替换 / split_run_types)已被服务端
|
|
2027
|
+
# 拒绝("split-first Version group updates are not supported")。
|
|
2028
|
+
env = _environment(environment)
|
|
2029
|
+
existing = _versioned_split(dataset, resolved_version, split, environment=environment)
|
|
2030
|
+
if existing is not None:
|
|
1933
2031
|
# 幂等:已存在同名 split(设计 §4.3)。
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
payload["created"] = False
|
|
1940
|
-
_emit_detail(payload, output_format)
|
|
1941
|
-
return
|
|
1942
|
-
_emit_detail(
|
|
1943
|
-
{"dataset": dataset, "version": resolved_version, "split": split, "created": False},
|
|
1944
|
-
output_format,
|
|
1945
|
-
)
|
|
2032
|
+
payload = dict(existing)
|
|
2033
|
+
payload.setdefault("dataset", dataset)
|
|
2034
|
+
payload.setdefault("version", resolved_version)
|
|
2035
|
+
payload["created"] = False
|
|
2036
|
+
_emit_detail(payload, output_format)
|
|
1946
2037
|
return
|
|
1947
2038
|
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
_patch_version(
|
|
1951
|
-
dataset,
|
|
1952
|
-
resolved_version,
|
|
1953
|
-
{"splits": names},
|
|
1954
|
-
stage="split create",
|
|
1955
|
-
environment=environment,
|
|
1956
|
-
metadata_model=_VERSION_SPLIT_METADATA_MODEL,
|
|
1957
|
-
)
|
|
2039
|
+
from .instance_commands import _sdk_read_kwargs
|
|
2040
|
+
from .irepo_sdk import _reason
|
|
1958
2041
|
|
|
1959
|
-
|
|
2042
|
+
repo = _dataset_repo()
|
|
2043
|
+
create_kwargs = _sdk_read_kwargs(repo.splits, "create", environment=env, metadata_model="")
|
|
1960
2044
|
if resolved_run_type:
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
)
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
)
|
|
1976
|
-
|
|
1977
|
-
_emit_detail(
|
|
1978
|
-
{
|
|
1979
|
-
"dataset": dataset,
|
|
1980
|
-
"version": resolved_version,
|
|
1981
|
-
"split": split,
|
|
1982
|
-
"run_type": resolved_run_type,
|
|
1983
|
-
"splits": names,
|
|
1984
|
-
"created": True,
|
|
1985
|
-
},
|
|
1986
|
-
output_format,
|
|
1987
|
-
)
|
|
2045
|
+
create_kwargs["run_type"] = resolved_run_type
|
|
2046
|
+
try:
|
|
2047
|
+
record = repo.splits.create(dataset, split, version=resolved_version, **create_kwargs)
|
|
2048
|
+
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
2049
|
+
raise _fail(
|
|
2050
|
+
f"Failed to create split {split} of dataset {dataset} version {resolved_version}: {_reason(exc)}",
|
|
2051
|
+
"Check the version name and write permission, then retry",
|
|
2052
|
+
) from exc
|
|
2053
|
+
payload = _model_dict(record) if record is not None else {}
|
|
2054
|
+
payload.setdefault("dataset", dataset)
|
|
2055
|
+
payload.setdefault("version", resolved_version)
|
|
2056
|
+
payload.setdefault("split", split)
|
|
2057
|
+
payload["created"] = True
|
|
2058
|
+
_emit_detail(payload, output_format)
|
|
1988
2059
|
|
|
1989
2060
|
|
|
1990
2061
|
@dataset_split_app.command("update")
|
|
@@ -2064,31 +2135,48 @@ def dataset_split_update(
|
|
|
2064
2135
|
)
|
|
2065
2136
|
return
|
|
2066
2137
|
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2138
|
+
# 版本化更新走独立 Split API(PATCH /datasets/splits/detail),预检用精确
|
|
2139
|
+
# split 身份(splits.get with version)——旧实现查 Version 内嵌 splits 再
|
|
2140
|
+
# PATCH Version,与独立 Split 记录不匹配,导致"已存在却报 not found"。
|
|
2141
|
+
env = _environment(environment)
|
|
2142
|
+
if env == "pre":
|
|
2143
|
+
raise _fail(
|
|
2144
|
+
f"Failed to update split {split} of dataset {dataset}: split records can only be changed in the online environment",
|
|
2145
|
+
"PATCH on split records only supports online; drop --environment pre",
|
|
2146
|
+
exit_code=2,
|
|
2147
|
+
)
|
|
2148
|
+
record = _versioned_split(dataset, resolved_version, split, environment=environment)
|
|
2149
|
+
if record is None:
|
|
2150
|
+
known = (
|
|
2151
|
+
", ".join(
|
|
2152
|
+
_split_names_of(
|
|
2153
|
+
_versioned_splits(
|
|
2154
|
+
dataset, resolved_version, stage="split update precheck",
|
|
2155
|
+
environment=environment,
|
|
2156
|
+
)
|
|
2157
|
+
)
|
|
2158
|
+
)
|
|
2159
|
+
or "<none>"
|
|
2160
|
+
)
|
|
2078
2161
|
raise _fail(
|
|
2079
2162
|
f"dataset {dataset} version {_version_label(resolved_version)} split {split}: "
|
|
2080
2163
|
f"update failed: not found",
|
|
2081
|
-
|
|
2164
|
+
f"Create it first: ap dataset split create {split} --dataset {dataset} "
|
|
2165
|
+
f"--version {resolved_version} (existing: {known})",
|
|
2082
2166
|
)
|
|
2083
2167
|
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2168
|
+
from .instance_commands import _sdk_read_kwargs
|
|
2169
|
+
from .irepo_sdk import _reason
|
|
2170
|
+
|
|
2171
|
+
repo = _dataset_repo()
|
|
2172
|
+
update_kwargs = _sdk_read_kwargs(repo.splits, "update", environment=env, metadata_model="")
|
|
2173
|
+
try:
|
|
2174
|
+
repo.splits.update(dataset, split, version=resolved_version, run_type=resolved_run_type, **update_kwargs)
|
|
2175
|
+
except Exception as exc: # noqa: BLE001 - 统一走设计 §5.1 的映射
|
|
2176
|
+
raise _fail(
|
|
2177
|
+
f"Failed to update split {split} of dataset {dataset} version {resolved_version}: {_reason(exc)}",
|
|
2178
|
+
"Write permission on this dataset is required; check the split name and the online environment, then retry",
|
|
2179
|
+
) from exc
|
|
2092
2180
|
_emit_detail(
|
|
2093
2181
|
{
|
|
2094
2182
|
"dataset": dataset,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""DatasetName 解析 Adapter:唯一入口委托给已安装的 InstanceRepo SDK。
|
|
2
|
+
|
|
3
|
+
AP CLI 不维护第三套 dataset 名解析:``instance_repo.paths.DatasetName`` 是客户端
|
|
4
|
+
路径契约的唯一事实源(单段 ``A`` / 两段 ``A/B`` / 多段树形 ``A/B/C``,与
|
|
5
|
+
apiserver 的 namespace guard 及 conformance 契约对齐)。SDK 缺失或过旧(没有
|
|
6
|
+
``DatasetName.parse`` 能力)时,给出明确的升级提示,不静默降级、不本地重写解析规则。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import unicodedata
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"DatasetNameError",
|
|
17
|
+
"DatasetNameUpgradeRequired",
|
|
18
|
+
"parse_dataset_name",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
#: SDK 缺失/无 DatasetName 能力时的升级提示(与 irepo_sdk 的 SdkMissing 同范式)。
|
|
22
|
+
_UPGRADE_HINT = (
|
|
23
|
+
"The installed instance-repo SDK does not provide DatasetName parsing "
|
|
24
|
+
"(single-segment L1 dataset names need it).\n"
|
|
25
|
+
"Install it with: pip install 'ap-client[dataset]' --upgrade"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
#: 多段(3+)名能力的首个 SDK 版本。旧 SDK(如 1.4.0)有 parse 但只认 1-2 段,
|
|
29
|
+
#: 对多段名会抛"one or two canonical segments"——此时应提示升级而非报名称非法。
|
|
30
|
+
_MULTI_LEVEL_SDK_VERSION = (1, 4, 1)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _sdk_supports_multi_level() -> bool:
|
|
34
|
+
"""已安装 instance-repo 是否支持多段数据集名(>= 1.4.1)。版本不可读时保守视为不支持。"""
|
|
35
|
+
try:
|
|
36
|
+
from importlib.metadata import version
|
|
37
|
+
|
|
38
|
+
installed = version("instance-repo")
|
|
39
|
+
except Exception: # noqa: BLE001 - 元数据缺失与旧 SDK 同等对待
|
|
40
|
+
return False
|
|
41
|
+
match = re.match(r"(\d+)\.(\d+)\.(\d+)", str(installed))
|
|
42
|
+
if not match:
|
|
43
|
+
return False
|
|
44
|
+
return tuple(int(part) for part in match.groups()) >= _MULTI_LEVEL_SDK_VERSION
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_structurally_valid_multi_level(value: str) -> bool:
|
|
48
|
+
"""值是否为"新 SDK 会接受"的 3+ 段名——即每个段都合法。
|
|
49
|
+
|
|
50
|
+
旧 SDK 对非法名(空段 a//b、非法字符 a*b)与多段名报同一文案,不能只按
|
|
51
|
+
段数判断:升级 SDK 也修不好空段/非法字符,必须保留原始校验错误。
|
|
52
|
+
这里保守复刻 SDK 的非法段规则作为 gate,只放行结构上合法的多段名。
|
|
53
|
+
"""
|
|
54
|
+
parts = value.split("/")
|
|
55
|
+
if len(parts) < 3 or any(not part for part in parts):
|
|
56
|
+
return False
|
|
57
|
+
for part in parts:
|
|
58
|
+
if (
|
|
59
|
+
part in {".", ".."}
|
|
60
|
+
or "\\" in part
|
|
61
|
+
or "*" in part
|
|
62
|
+
or "?" in part
|
|
63
|
+
or any(unicodedata.category(char) == "Cc" for char in part)
|
|
64
|
+
):
|
|
65
|
+
return False
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DatasetNameUpgradeRequired(RuntimeError):
|
|
70
|
+
"""已安装的 instance-repo SDK 缺少 DatasetName 能力,必须升级。"""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class DatasetNameError(ValueError):
|
|
74
|
+
"""Dataset 名不符合 IR DatasetName 契约(单段或两段、无非法段)。"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_dataset_name(value: str) -> Any:
|
|
78
|
+
"""Parse ``value`` through the installed IR ``DatasetName`` capability.
|
|
79
|
+
|
|
80
|
+
Returns the SDK ``DatasetName`` (``canonical`` / ``l1`` / ``l2`` /
|
|
81
|
+
``segments`` / ``leaf``)。Raises :class:`DatasetNameUpgradeRequired` when the
|
|
82
|
+
installed SDK lacks the capability (包括只认 1-2 段的旧 SDK 拒绝多段名);
|
|
83
|
+
raises :class:`DatasetNameError` when the value is not a valid name.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
from instance_repo.paths import DatasetName
|
|
87
|
+
except ImportError as exc:
|
|
88
|
+
raise DatasetNameUpgradeRequired(_UPGRADE_HINT) from exc
|
|
89
|
+
parse = getattr(DatasetName, "parse", None)
|
|
90
|
+
if not callable(parse):
|
|
91
|
+
raise DatasetNameUpgradeRequired(_UPGRADE_HINT)
|
|
92
|
+
try:
|
|
93
|
+
return parse(value)
|
|
94
|
+
except Exception as exc: # noqa: BLE001 - SDK 校验失败统一转成 CLI 契约
|
|
95
|
+
# 旧 SDK(1.4.0 及更早)只认 1-2 段:结构上合法的多段名被拒时给出
|
|
96
|
+
# 升级提示。非法名(空段/非法字符)无论新旧 SDK 都拒绝,保留原始
|
|
97
|
+
# 校验错误为 DatasetNameError——升级修不好这类名称。
|
|
98
|
+
if (
|
|
99
|
+
isinstance(value, str)
|
|
100
|
+
and _is_structurally_valid_multi_level(value)
|
|
101
|
+
and not _sdk_supports_multi_level()
|
|
102
|
+
):
|
|
103
|
+
raise DatasetNameUpgradeRequired(_UPGRADE_HINT) from exc
|
|
104
|
+
raise DatasetNameError(str(exc)) from exc
|