instance-repo 1.3.2.dev1__tar.gz → 1.3.2.dev3__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.
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/PKG-INFO +1 -1
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/__init__.py +1 -1
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/_routing.py +4 -4
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/datasets.py +7 -3
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/splits.py +23 -3
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/versions.py +13 -3
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/models.py +2 -3
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/repo.py +2 -1
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/store/oss.py +12 -12
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/store/oss_discovery.py +151 -92
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/transport.py +17 -11
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo.egg-info/PKG-INFO +1 -1
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/pyproject.toml +1 -1
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/README.md +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/_site_defaults.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/artifacts.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/bootstrap.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/cache.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/cli.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/__init__.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/benchmarks.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/config.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/images.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/instances.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/reports.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/scaffold.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/clients/workflows.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/concurrency.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/content.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/endpoints.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/errors.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/image.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/layout.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/loader.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/paths.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/release.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/retry.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/store/__init__.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/store/acr.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/store/base.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/store/registry.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/tasktoml.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo/validate.py +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo.egg-info/SOURCES.txt +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo.egg-info/dependency_links.txt +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo.egg-info/entry_points.txt +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo.egg-info/requires.txt +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo.egg-info/top_level.txt +0 -0
- {instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/setup.cfg +0 -0
|
@@ -45,14 +45,14 @@ def _split_scheme(endpoint: str) -> tuple[str, str]:
|
|
|
45
45
|
return "", endpoint
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def _probe_private_endpoint(endpoint: str, timeout: float =
|
|
48
|
+
def _probe_private_endpoint(endpoint: str, timeout: float = 0.5) -> bool:
|
|
49
49
|
"""Quick TCP connect probe to decide whether a private endpoint is reachable.
|
|
50
50
|
|
|
51
51
|
Performs a raw TCP connect (no TLS handshake) to the endpoint host/port.
|
|
52
52
|
Returns True if the connect succeeds, False on any error. The default
|
|
53
|
-
timeout is
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
timeout is 0.5 seconds — 内网 TCP 握手通常在几十毫秒内完成,0.5s 足够
|
|
54
|
+
判定可达性,同时把公网环境下私网不可达的探测开销从 3s 压到 0.5s
|
|
55
|
+
(结果按 store 缓存 5 分钟,仅端点变更或缓存过期才重探)。
|
|
56
56
|
"""
|
|
57
57
|
_, host = _split_scheme(endpoint)
|
|
58
58
|
# Determine port: default 443 for https, 80 for http.
|
|
@@ -16,10 +16,13 @@ class DatasetsClient:
|
|
|
16
16
|
_BASE = "/apis/v1/datasets/series"
|
|
17
17
|
|
|
18
18
|
def __init__(self, transport, *, content_store=None,
|
|
19
|
-
oss_prefix: str = "swe/datasets"
|
|
19
|
+
oss_prefix: str = "swe/datasets",
|
|
20
|
+
metadata_model: str | None = None) -> None:
|
|
20
21
|
self._t = transport
|
|
21
22
|
self._content = content_store
|
|
22
23
|
self._oss_prefix = oss_prefix
|
|
24
|
+
# OSS 回退探测的布局语义(split_first 默认;version_first 显式切换)
|
|
25
|
+
self._metadata_model = metadata_model or "split_first"
|
|
23
26
|
|
|
24
27
|
def list(self, l1: str | None = None) -> list[DatasetSeries]:
|
|
25
28
|
"""列出 dataset(便利语义:自动翻页取全量,见 :meth:`list_paged` 单页对照)。
|
|
@@ -398,9 +401,10 @@ class DatasetsClient:
|
|
|
398
401
|
parse_dataset_level, build_synthetic_dataset_detail,
|
|
399
402
|
_warn_if_truncated)
|
|
400
403
|
info = parse_dataset_level(
|
|
401
|
-
self._content, name, oss_prefix=self._oss_prefix
|
|
404
|
+
self._content, name, oss_prefix=self._oss_prefix,
|
|
405
|
+
metadata_model=self._metadata_model)
|
|
402
406
|
_warn_if_truncated(info, name, 500)
|
|
403
|
-
if not info.versions:
|
|
407
|
+
if not info.versions and not info.unversioned_splits:
|
|
404
408
|
from ..errors import DatasetNotFound
|
|
405
409
|
raise DatasetNotFound(f"dataset {name!r} not found in OSS")
|
|
406
410
|
return build_synthetic_dataset_detail(
|
|
@@ -264,16 +264,23 @@ class SplitsClient:
|
|
|
264
264
|
注意:回退场景(dataset 未注册)下 ``version_scope`` 是 MongoDB 语义,
|
|
265
265
|
OSS 探测无从区分 versioned/unversioned——**忽略 scope 并返回全部探测结果**
|
|
266
266
|
(G4:否则 ap CLI 无 --version 的 split list 恒空)。``version`` 非空时
|
|
267
|
-
仍精确匹配该 version
|
|
267
|
+
仍精确匹配该 version;version 为空时额外包含 split_first 无版本 split
|
|
268
|
+
(version="" 归属)。
|
|
268
269
|
"""
|
|
269
270
|
from ..store.oss_discovery import (
|
|
270
271
|
parse_dataset_level, build_synthetic_split,
|
|
271
272
|
_in_memory_paged, _warn_if_truncated,
|
|
272
273
|
)
|
|
273
274
|
info = parse_dataset_level(
|
|
274
|
-
self._content, dataset, oss_prefix=self._oss_prefix
|
|
275
|
+
self._content, dataset, oss_prefix=self._oss_prefix,
|
|
276
|
+
metadata_model=self._discovery_mm())
|
|
275
277
|
_warn_if_truncated(info, dataset, 500)
|
|
276
278
|
splits: list[DatasetSplit] = []
|
|
279
|
+
# split_first 无版本布局:dataset 根下直接是 split 目录
|
|
280
|
+
if not version:
|
|
281
|
+
for us in info.unversioned_splits:
|
|
282
|
+
splits.append(build_synthetic_split(
|
|
283
|
+
dataset, "", us.name, instance_count=us.instance_count))
|
|
277
284
|
for v in info.versions:
|
|
278
285
|
if version and v.version != version:
|
|
279
286
|
continue
|
|
@@ -295,8 +302,14 @@ class SplitsClient:
|
|
|
295
302
|
parse_dataset_level, build_synthetic_split, _warn_if_truncated,
|
|
296
303
|
)
|
|
297
304
|
info = parse_dataset_level(
|
|
298
|
-
self._content, dataset, oss_prefix=self._oss_prefix
|
|
305
|
+
self._content, dataset, oss_prefix=self._oss_prefix,
|
|
306
|
+
metadata_model=self._discovery_mm())
|
|
299
307
|
_warn_if_truncated(info, dataset, 500)
|
|
308
|
+
if not version:
|
|
309
|
+
for us in info.unversioned_splits:
|
|
310
|
+
if us.name == split_name:
|
|
311
|
+
return build_synthetic_split(
|
|
312
|
+
dataset, "", us.name, instance_count=us.instance_count)
|
|
300
313
|
for v in info.versions:
|
|
301
314
|
if version and v.version != version:
|
|
302
315
|
continue
|
|
@@ -309,6 +322,13 @@ class SplitsClient:
|
|
|
309
322
|
dataset, v.version, split_name)
|
|
310
323
|
return None
|
|
311
324
|
|
|
325
|
+
def _discovery_mm(self) -> str:
|
|
326
|
+
"""OSS 回退探测的布局语义(split_first 默认;version_first 显式切换)。"""
|
|
327
|
+
from ..layout import normalize_metadata_model
|
|
328
|
+
return normalize_metadata_model(
|
|
329
|
+
getattr(self._p, "metadata_model", None)
|
|
330
|
+
if self._p is not None else None)
|
|
331
|
+
|
|
312
332
|
# ── 写 ──
|
|
313
333
|
|
|
314
334
|
def _post_create(self, dataset: str, split: str, version: str,
|
|
@@ -895,9 +895,11 @@ class VersionsClient:
|
|
|
895
895
|
from ..store.oss_discovery import (
|
|
896
896
|
parse_dataset_level, build_synthetic_version, _warn_if_truncated)
|
|
897
897
|
info = parse_dataset_level(
|
|
898
|
-
self._content, dataset, oss_prefix=self._oss_prefix
|
|
898
|
+
self._content, dataset, oss_prefix=self._oss_prefix,
|
|
899
|
+
metadata_model=self._discovery_mm())
|
|
899
900
|
_warn_if_truncated(info, dataset, 500)
|
|
900
|
-
|
|
901
|
+
# split_first 无版本布局(只有 split)也是发现成功,只是没有版本可列
|
|
902
|
+
if not info.versions and not info.unversioned_splits:
|
|
901
903
|
from ..errors import DatasetNotFound
|
|
902
904
|
raise DatasetNotFound(f"dataset {dataset!r} not found in OSS")
|
|
903
905
|
return [build_synthetic_version(dataset, v) for v in info.versions]
|
|
@@ -915,7 +917,8 @@ class VersionsClient:
|
|
|
915
917
|
from ..store.oss_discovery import (
|
|
916
918
|
parse_dataset_level, build_synthetic_version, _warn_if_truncated)
|
|
917
919
|
info = parse_dataset_level(
|
|
918
|
-
self._content, dataset, oss_prefix=self._oss_prefix
|
|
920
|
+
self._content, dataset, oss_prefix=self._oss_prefix,
|
|
921
|
+
metadata_model=self._discovery_mm())
|
|
919
922
|
_warn_if_truncated(info, dataset, 500)
|
|
920
923
|
for v in info.versions:
|
|
921
924
|
if v.version == version:
|
|
@@ -924,6 +927,13 @@ class VersionsClient:
|
|
|
924
927
|
raise DatasetNotFound(
|
|
925
928
|
f"version {version!r} not found in OSS for dataset {dataset!r}")
|
|
926
929
|
|
|
930
|
+
def _discovery_mm(self) -> str:
|
|
931
|
+
"""OSS 回退探测的布局语义(split_first 默认;version_first 显式切换)。"""
|
|
932
|
+
from ..layout import normalize_metadata_model
|
|
933
|
+
return normalize_metadata_model(
|
|
934
|
+
getattr(self._p, "metadata_model", None)
|
|
935
|
+
if self._p is not None else None)
|
|
936
|
+
|
|
927
937
|
|
|
928
938
|
# 与 apiserver `DatasetVersionListItem`(pkg/server/dataset_service.go:315)对齐:
|
|
929
939
|
# 列表项带 instance_count/split_count/updated_at/last_ingested_at,详情接口才带
|
|
@@ -21,9 +21,8 @@ RUN_TYPE_VALUES = ("train", "eval")
|
|
|
21
21
|
STORAGE_TYPE_VALUES = ("oss",)
|
|
22
22
|
# permission 是签发 OSS 凭据时的**权限封顶**(apiserver credential_handler 的 permission
|
|
23
23
|
# 字段):只能把本次令牌收窄到不高于调用方真实角色,绝不提权。ACR 分支不支持该字段。
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
PERMISSION_VALUES = ("admin", "writer", "reader", "list")
|
|
24
|
+
# 发现模式(list-only)走独立字段 list_only,不与封顶枚举混用。
|
|
25
|
+
PERMISSION_VALUES = ("admin", "writer", "reader")
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
def canonical_json(obj: Any) -> str:
|
|
@@ -84,7 +84,8 @@ class Repo:
|
|
|
84
84
|
self.datasets = DatasetsClient(
|
|
85
85
|
self.transport,
|
|
86
86
|
content_store=self._content,
|
|
87
|
-
oss_prefix=self.profile.oss_prefix
|
|
87
|
+
oss_prefix=self.profile.oss_prefix,
|
|
88
|
+
metadata_model=self.profile.metadata_model)
|
|
88
89
|
self.versions = VersionsClient(
|
|
89
90
|
self.transport, self.profile,
|
|
90
91
|
content_store=self._content,
|
|
@@ -284,9 +284,9 @@ class OssContentStore(BaseContentStore):
|
|
|
284
284
|
self._retired_buckets.pop()
|
|
285
285
|
|
|
286
286
|
def _credentials(self, prefix: str, dataset: str | None,
|
|
287
|
-
|
|
287
|
+
list_only: bool = False):
|
|
288
288
|
return self._t.get_oss_credentials(
|
|
289
|
-
self._bucket, prefix, dataset_name=dataset,
|
|
289
|
+
self._bucket, prefix, dataset_name=dataset, list_only=list_only)
|
|
290
290
|
|
|
291
291
|
def _open(self, prefix: str, dataset: str | None = None):
|
|
292
292
|
"""Compatibility helper used by prewarm and legacy private tests."""
|
|
@@ -297,7 +297,7 @@ class OssContentStore(BaseContentStore):
|
|
|
297
297
|
|
|
298
298
|
def _execute_oss(self, intent: _OperationIntent, prefix: str,
|
|
299
299
|
dataset: str | None, operation, *,
|
|
300
|
-
|
|
300
|
+
list_only: bool = False):
|
|
301
301
|
"""Execute one logical OSS operation using one STS acquisition.
|
|
302
302
|
|
|
303
303
|
Non-download operations perform one complete bounded private attempt and only retry the
|
|
@@ -306,13 +306,13 @@ class OssContentStore(BaseContentStore):
|
|
|
306
306
|
stable private-network-required error.
|
|
307
307
|
|
|
308
308
|
Args:
|
|
309
|
-
|
|
310
|
-
Passed through to :meth:`_credentials
|
|
309
|
+
list_only: Discovery-mode switch: True 时签发仅含 ListObjects 的令牌
|
|
310
|
+
(OSS 回退发现专用)。Passed through to :meth:`_credentials`.
|
|
311
311
|
"""
|
|
312
312
|
if intent is not _OperationIntent.DOWNLOAD and intent is not _OperationIntent.NON_DOWNLOAD:
|
|
313
313
|
raise ValueError(f"unknown OSS operation intent: {intent!r}")
|
|
314
314
|
|
|
315
|
-
sts = self._credentials(prefix, dataset,
|
|
315
|
+
sts = self._credentials(prefix, dataset, list_only=list_only)
|
|
316
316
|
original_endpoint = sts.get("endpoint", "")
|
|
317
317
|
route = _plan_oss_route(original_endpoint)
|
|
318
318
|
|
|
@@ -451,7 +451,7 @@ class OssContentStore(BaseContentStore):
|
|
|
451
451
|
lambda bucket: bucket.object_exists(key),
|
|
452
452
|
)
|
|
453
453
|
|
|
454
|
-
def list_objects(self, prefix: str, *,
|
|
454
|
+
def list_objects(self, prefix: str, *, list_only: bool = False) -> list[str]:
|
|
455
455
|
def list_all(bucket):
|
|
456
456
|
keys: list[str] = []
|
|
457
457
|
marker = ""
|
|
@@ -466,9 +466,9 @@ class OssContentStore(BaseContentStore):
|
|
|
466
466
|
|
|
467
467
|
return self._execute_oss(
|
|
468
468
|
_OperationIntent.NON_DOWNLOAD, prefix, None, list_all,
|
|
469
|
-
|
|
469
|
+
list_only=list_only)
|
|
470
470
|
|
|
471
|
-
def list_prefixes(self, prefix: str, *,
|
|
471
|
+
def list_prefixes(self, prefix: str, *, list_only: bool = False) -> list[str]:
|
|
472
472
|
"""List immediate child *directories* under *prefix* using ``delimiter='/'``.
|
|
473
473
|
|
|
474
474
|
Returns stripped directory names (without the parent prefix or trailing
|
|
@@ -500,10 +500,10 @@ class OssContentStore(BaseContentStore):
|
|
|
500
500
|
|
|
501
501
|
return self._execute_oss(
|
|
502
502
|
_OperationIntent.NON_DOWNLOAD, prefix, None, list_dirs,
|
|
503
|
-
|
|
503
|
+
list_only=list_only)
|
|
504
504
|
|
|
505
505
|
def list_level(self, prefix: str, *,
|
|
506
|
-
|
|
506
|
+
list_only: bool = False) -> tuple[list[str], list[str]]:
|
|
507
507
|
"""Single-level listing with ``delimiter='/'``.
|
|
508
508
|
|
|
509
509
|
Returns ``(dir_names, file_keys)`` where *dir_names* are stripped
|
|
@@ -534,7 +534,7 @@ class OssContentStore(BaseContentStore):
|
|
|
534
534
|
|
|
535
535
|
return self._execute_oss(
|
|
536
536
|
_OperationIntent.NON_DOWNLOAD, prefix, None, list_one_level,
|
|
537
|
-
|
|
537
|
+
list_only=list_only)
|
|
538
538
|
|
|
539
539
|
# ── directory-level helpers(自定义格式实例 = 目录级上传/下载)──
|
|
540
540
|
def put_directory(self, local_dir: Path, oss_prefix: str, *,
|
|
@@ -63,6 +63,14 @@ class OssVersionInfo:
|
|
|
63
63
|
default_instance_count: int = 0
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
@dataclass
|
|
67
|
+
class OssUnversionedSplit:
|
|
68
|
+
"""split_first 无版本布局:dataset 根下直接是 split 目录(无 version 层)。"""
|
|
69
|
+
name: str
|
|
70
|
+
has_assets: bool # 同层 {name}-assets/ 存在(split 级 content)
|
|
71
|
+
instance_count: int # {name}/ 下直接 .json 数
|
|
72
|
+
|
|
73
|
+
|
|
66
74
|
@dataclass
|
|
67
75
|
class OssDatasetLevelInfo:
|
|
68
76
|
"""Overall dataset structure discovered from OSS directory layout."""
|
|
@@ -70,6 +78,12 @@ class OssDatasetLevelInfo:
|
|
|
70
78
|
versions: list[OssVersionInfo]
|
|
71
79
|
total_instance_count: int
|
|
72
80
|
truncated: bool = False
|
|
81
|
+
# split_first 无版本布局下的 split(version="" 语义)
|
|
82
|
+
unversioned_splits: list[OssUnversionedSplit] = None # type: ignore[assignment]
|
|
83
|
+
|
|
84
|
+
def __post_init__(self):
|
|
85
|
+
if self.unversioned_splits is None:
|
|
86
|
+
self.unversioned_splits = []
|
|
73
87
|
|
|
74
88
|
|
|
75
89
|
# ---------------------------------------------------------------------------
|
|
@@ -185,11 +199,18 @@ def parse_dataset_level(
|
|
|
185
199
|
*,
|
|
186
200
|
oss_prefix: str = "swe/datasets",
|
|
187
201
|
max_versions: int = 500,
|
|
202
|
+
metadata_model: str = "split_first",
|
|
188
203
|
) -> OssDatasetLevelInfo:
|
|
189
204
|
"""Discover version/split structure from OSS directory layout.
|
|
190
205
|
|
|
191
|
-
Performs
|
|
192
|
-
|
|
206
|
+
Performs ``1 + min(N, max_versions)`` LIST calls where *N* is the number
|
|
207
|
+
of Level 1 entries; the per-group probes run concurrently (8-way).
|
|
208
|
+
|
|
209
|
+
语义消歧(``metadata_model``):
|
|
210
|
+
- ``split_first``(SDK 默认):level-1 目录 `X/` 下**只有 .json、无子目录**
|
|
211
|
+
时,`X` 是无版本 split(`{split}/{id}.json` 契约形态);含子目录时是
|
|
212
|
+
version(子目录为其 split)。
|
|
213
|
+
- ``version_first``(显式切换):level-1 目录一律视为 version(旧行为)。
|
|
193
214
|
|
|
194
215
|
Args:
|
|
195
216
|
content_store: :class:`~instance_repo.store.oss.OssContentStore` instance.
|
|
@@ -197,6 +218,8 @@ def parse_dataset_level(
|
|
|
197
218
|
oss_prefix: Root prefix inside the bucket (default ``"swe/datasets"``).
|
|
198
219
|
max_versions: Upper bound on the number of versions to probe. When
|
|
199
220
|
exceeded the result is truncated and ``truncated=True`` is set.
|
|
221
|
+
metadata_model: ``split_first``(默认)or ``version_first``;决定
|
|
222
|
+
level-1 目录的语义归属(见上)。
|
|
200
223
|
|
|
201
224
|
Returns:
|
|
202
225
|
:class:`OssDatasetLevelInfo` with the discovered version/split tree.
|
|
@@ -211,7 +234,7 @@ def parse_dataset_level(
|
|
|
211
234
|
# -- Level 1: list immediate children under the dataset root -----------
|
|
212
235
|
try:
|
|
213
236
|
level1_names, level1_all_keys = content_store.list_level(
|
|
214
|
-
root,
|
|
237
|
+
root, list_only=True)
|
|
215
238
|
except TransportError as e:
|
|
216
239
|
if e.status == 403:
|
|
217
240
|
# OSS fallback requires ListObjects permission, which is only granted
|
|
@@ -231,90 +254,104 @@ def parse_dataset_level(
|
|
|
231
254
|
f"dataset {dataset!r} not found in OSS under {root!r}"
|
|
232
255
|
)
|
|
233
256
|
|
|
234
|
-
# -- Group level 1 names into
|
|
257
|
+
# -- Group level 1 names into candidates -------------------------------
|
|
235
258
|
l1_groups = _group_level_names(level1_names)
|
|
236
259
|
|
|
237
|
-
if not l1_groups
|
|
260
|
+
if not l1_groups:
|
|
261
|
+
if level1_json_keys:
|
|
262
|
+
# 实例 .json 直接位于 dataset 根下(无 version/split 层)是**非法布局**:
|
|
263
|
+
# paths.py 契约要求 metadata 至少带 version 层(version_first)或
|
|
264
|
+
# split 层(split_first)。不兼容识别,明确报错引导上传方修正。
|
|
265
|
+
raise DatasetNotFound(
|
|
266
|
+
f"dataset {dataset!r} exists in OSS but has no recognizable "
|
|
267
|
+
f"version/split structure: {len(level1_json_keys)} .json file(s) "
|
|
268
|
+
f"directly under {root!r} (expected {root!r}{{version}}/[{{split}}/] "
|
|
269
|
+
f"layout per paths.py contract)")
|
|
238
270
|
raise DatasetNotFound(
|
|
239
271
|
f"dataset {dataset!r} exists in OSS but has no recognizable "
|
|
240
272
|
f"version/split structure under {root!r}"
|
|
241
273
|
)
|
|
242
274
|
|
|
243
275
|
# Sort for deterministic output.
|
|
244
|
-
|
|
276
|
+
names = sorted(l1_groups.keys())
|
|
245
277
|
|
|
246
278
|
# -- Truncation protection ---------------------------------------------
|
|
247
279
|
truncated = False
|
|
248
|
-
if len(
|
|
249
|
-
|
|
280
|
+
if len(names) > max_versions:
|
|
281
|
+
names = names[:max_versions]
|
|
250
282
|
truncated = True
|
|
251
283
|
|
|
252
|
-
|
|
253
|
-
versions: list[OssVersionInfo] = []
|
|
254
|
-
total_instance_count = 0
|
|
255
|
-
|
|
256
|
-
for v_name in version_names:
|
|
257
|
-
grp = l1_groups[v_name]
|
|
258
|
-
version_dir = f"{root}{v_name}/"
|
|
284
|
+
split_first = (metadata_model or "split_first") != "version_first"
|
|
259
285
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
286
|
+
# -- Level 2: probe each candidate (concurrent) ------------------------
|
|
287
|
+
# 每个 group 的完整分析(level-2 列举 + 逐 split 计数)都在 probe 内完成,
|
|
288
|
+
# 使大 version 数数据集的探测全程并行,避免串行逐 split 计数拖慢整链。
|
|
289
|
+
def probe(name: str):
|
|
290
|
+
"""分析单个 level-1 group,返回 OssVersionInfo / OssUnversionedSplit。"""
|
|
291
|
+
grp = l1_groups[name]
|
|
292
|
+
group_dir = f"{root}{name}/"
|
|
264
293
|
try:
|
|
265
|
-
level2_names = content_store.
|
|
294
|
+
level2_names, level2_keys = content_store.list_level(
|
|
295
|
+
group_dir, list_only=True)
|
|
266
296
|
except Exception: # noqa: BLE001
|
|
267
|
-
level2_names = []
|
|
268
|
-
|
|
297
|
+
level2_names, level2_keys = [], []
|
|
269
298
|
l2_groups = _group_level_names(level2_names)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
instance_count = len(v_json_keys)
|
|
286
|
-
except Exception: # noqa: BLE001
|
|
287
|
-
has_default_instances = False
|
|
288
|
-
default_instance_count = 0
|
|
289
|
-
instance_count = 0
|
|
290
|
-
|
|
291
|
-
# Count instances in each named split directory.
|
|
299
|
+
direct_json = _direct_json_keys(level2_keys, group_dir)
|
|
300
|
+
subdirs = [g for g in l2_groups.values() if g.has_dir or g.has_assets]
|
|
301
|
+
|
|
302
|
+
# split_first 无版本布局:`{name}/` 下只有 .json、无子目录 →
|
|
303
|
+
# 无版本 split(contract: {split}/{id}.json + {split}-assets/)。
|
|
304
|
+
if split_first and grp.has_dir and not subdirs and direct_json:
|
|
305
|
+
return OssUnversionedSplit(
|
|
306
|
+
name=name,
|
|
307
|
+
has_assets=grp.has_assets,
|
|
308
|
+
instance_count=len(direct_json),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# version 路径(version_first 或含子目录的 split_first 形态)。
|
|
312
|
+
splits: list[str] = sorted(g.name for g in subdirs)
|
|
313
|
+
instance_count = len(direct_json)
|
|
292
314
|
for s_name in splits:
|
|
293
|
-
split_dir = f"{
|
|
315
|
+
split_dir = f"{group_dir}{s_name}/"
|
|
294
316
|
try:
|
|
295
317
|
_s_dirs, s_keys = content_store.list_level(
|
|
296
|
-
split_dir,
|
|
297
|
-
|
|
298
|
-
instance_count += len(s_json)
|
|
318
|
+
split_dir, list_only=True)
|
|
319
|
+
instance_count += len(_direct_json_keys(s_keys, split_dir))
|
|
299
320
|
except Exception: # noqa: BLE001
|
|
300
321
|
pass # best-effort
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
versions.append(OssVersionInfo(
|
|
305
|
-
version=v_name,
|
|
306
|
-
has_three_layer_content=has_three_layer_content,
|
|
322
|
+
return OssVersionInfo(
|
|
323
|
+
version=name,
|
|
324
|
+
has_three_layer_content=grp.has_assets,
|
|
307
325
|
splits=splits,
|
|
308
|
-
has_default_instances=
|
|
326
|
+
has_default_instances=len(direct_json) > 0,
|
|
309
327
|
instance_count=instance_count,
|
|
310
|
-
default_instance_count=
|
|
311
|
-
)
|
|
328
|
+
default_instance_count=len(direct_json),
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
from ..concurrency import map_concurrent
|
|
333
|
+
results = map_concurrent(probe, names, concurrency=16)
|
|
334
|
+
except Exception: # noqa: BLE001 - 并发不可用时退化为串行
|
|
335
|
+
results = [probe(n) for n in names]
|
|
336
|
+
|
|
337
|
+
versions: list[OssVersionInfo] = []
|
|
338
|
+
unversioned_splits: list[OssUnversionedSplit] = []
|
|
339
|
+
total_instance_count = 0
|
|
340
|
+
|
|
341
|
+
for entry in results:
|
|
342
|
+
if isinstance(entry, OssUnversionedSplit):
|
|
343
|
+
unversioned_splits.append(entry)
|
|
344
|
+
total_instance_count += entry.instance_count
|
|
345
|
+
else:
|
|
346
|
+
versions.append(entry)
|
|
347
|
+
total_instance_count += entry.instance_count
|
|
312
348
|
|
|
313
349
|
return OssDatasetLevelInfo(
|
|
314
350
|
dataset=dataset,
|
|
315
351
|
versions=versions,
|
|
316
352
|
total_instance_count=total_instance_count,
|
|
317
353
|
truncated=truncated,
|
|
354
|
+
unversioned_splits=unversioned_splits,
|
|
318
355
|
)
|
|
319
356
|
|
|
320
357
|
|
|
@@ -323,9 +360,10 @@ def _warn_if_truncated(info: OssDatasetLevelInfo, dataset: str, max_versions: in
|
|
|
323
360
|
if info.truncated:
|
|
324
361
|
import logging
|
|
325
362
|
logging.getLogger(__name__).warning(
|
|
326
|
-
"dataset %r has more than %d
|
|
363
|
+
"dataset %r has more than %d level-1 entries; OSS discovery results "
|
|
327
364
|
"are truncated (showing %d of >%d)",
|
|
328
|
-
dataset, max_versions,
|
|
365
|
+
dataset, max_versions,
|
|
366
|
+
len(info.versions) + len(info.unversioned_splits), max_versions)
|
|
329
367
|
|
|
330
368
|
|
|
331
369
|
# ---------------------------------------------------------------------------
|
|
@@ -389,6 +427,10 @@ def build_synthetic_dataset_detail(
|
|
|
389
427
|
if v.has_default_instances:
|
|
390
428
|
all_splits.add("default")
|
|
391
429
|
total_instances += v.instance_count
|
|
430
|
+
# split_first 无版本布局:level-1 目录即 split
|
|
431
|
+
for us in info.unversioned_splits:
|
|
432
|
+
all_splits.add(us.name)
|
|
433
|
+
total_instances += us.instance_count
|
|
392
434
|
|
|
393
435
|
available_envs: list[str] = [storage_env] if storage_env else []
|
|
394
436
|
|
|
@@ -450,10 +492,11 @@ def build_synthetic_split(
|
|
|
450
492
|
instance_count: int = 0,
|
|
451
493
|
) -> DatasetSplit:
|
|
452
494
|
"""Build a :class:`DatasetSplit` from OSS discovery (``registered=False``)."""
|
|
495
|
+
base = f"swe/datasets/{dataset}" + (f"/{version}" if version else "")
|
|
453
496
|
if split_name == "default":
|
|
454
|
-
storage_path = f"
|
|
497
|
+
storage_path = f"{base}/"
|
|
455
498
|
else:
|
|
456
|
-
storage_path = f"
|
|
499
|
+
storage_path = f"{base}/{split_name}/"
|
|
457
500
|
|
|
458
501
|
return DatasetSplit(
|
|
459
502
|
dataset=dataset,
|
|
@@ -468,6 +511,16 @@ def build_synthetic_split(
|
|
|
468
511
|
)
|
|
469
512
|
|
|
470
513
|
|
|
514
|
+
def _metadata_prefix(dataset: str, version: str, split: str | None,
|
|
515
|
+
oss_prefix: str) -> str:
|
|
516
|
+
"""拼接 metadata 列举前缀;version 为空时表示 split_first 无版本布局。"""
|
|
517
|
+
l1, l2 = P.split_dataset(dataset)
|
|
518
|
+
base = f"{oss_prefix}/{l1}/{l2}" + (f"/{version}" if version else "")
|
|
519
|
+
if split and split != "default":
|
|
520
|
+
return f"{base}/{split}/"
|
|
521
|
+
return f"{base}/"
|
|
522
|
+
|
|
523
|
+
|
|
471
524
|
def build_synthetic_instances(
|
|
472
525
|
content_store,
|
|
473
526
|
dataset: str,
|
|
@@ -475,22 +528,18 @@ def build_synthetic_instances(
|
|
|
475
528
|
split: str | None = None,
|
|
476
529
|
*,
|
|
477
530
|
oss_prefix: str = "swe/datasets",
|
|
531
|
+
read_content: bool = False,
|
|
478
532
|
) -> list[DatasetInstance]:
|
|
479
533
|
"""List and build :class:`DatasetInstance` objects from OSS metadata JSONs.
|
|
480
534
|
|
|
481
535
|
For each ``{instance_id}.json`` found directly under the target prefix, a
|
|
482
|
-
``DatasetInstance`` is constructed.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
left at their defaults (best-effort).
|
|
536
|
+
``DatasetInstance`` is constructed. ``read_content=True`` 时尝试读 JSON
|
|
537
|
+
回填 tags/difficulty/docker_image;默认 False——发现模式(list-only STS)
|
|
538
|
+
无 GetObject,逐实例 GET 必然失败,跳过可省 N 次无谓的 STS 签发与下载。
|
|
486
539
|
"""
|
|
487
|
-
|
|
488
|
-
if split and split != "default":
|
|
489
|
-
metadata_prefix = f"{oss_prefix}/{l1}/{l2}/{version}/{split}/"
|
|
490
|
-
else:
|
|
491
|
-
metadata_prefix = f"{oss_prefix}/{l1}/{l2}/{version}/"
|
|
540
|
+
metadata_prefix = _metadata_prefix(dataset, version, split, oss_prefix)
|
|
492
541
|
|
|
493
|
-
all_keys = content_store.list_objects(metadata_prefix,
|
|
542
|
+
all_keys = content_store.list_objects(metadata_prefix, list_only=True)
|
|
494
543
|
json_keys = _direct_json_keys(all_keys, metadata_prefix)
|
|
495
544
|
|
|
496
545
|
instances: list[DatasetInstance] = []
|
|
@@ -503,15 +552,13 @@ def build_synthetic_instances(
|
|
|
503
552
|
split=split or "default",
|
|
504
553
|
registered=False,
|
|
505
554
|
)
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
inst.docker_image = data.get("docker_image")
|
|
514
|
-
inst.extend_meta = data.get("extend_meta", {})
|
|
555
|
+
if read_content:
|
|
556
|
+
data = _read_json_object(content_store, key)
|
|
557
|
+
if data:
|
|
558
|
+
inst.tags = data.get("tags", {})
|
|
559
|
+
inst.difficulty = data.get("difficulty")
|
|
560
|
+
inst.docker_image = data.get("docker_image")
|
|
561
|
+
inst.extend_meta = data.get("extend_meta", {})
|
|
515
562
|
instances.append(inst)
|
|
516
563
|
|
|
517
564
|
return instances
|
|
@@ -525,19 +572,30 @@ def build_synthetic_instance(
|
|
|
525
572
|
split: str | None = None,
|
|
526
573
|
*,
|
|
527
574
|
oss_prefix: str = "swe/datasets",
|
|
575
|
+
read_content: bool = False,
|
|
528
576
|
) -> DatasetInstance | None:
|
|
529
577
|
"""Build a single :class:`DatasetInstance` by exact key lookup in OSS.
|
|
530
578
|
|
|
531
|
-
Returns ``None`` if the metadata JSON does not exist.
|
|
579
|
+
Returns ``None`` if the metadata JSON does not exist. ``read_content``
|
|
580
|
+
语义同 :func:`build_synthetic_instances`(发现模式默认跳过 JSON 回填)。
|
|
532
581
|
"""
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
582
|
+
if version:
|
|
583
|
+
key = P.metadata_key(
|
|
584
|
+
dataset, version, instance_id, split, prefix=oss_prefix)
|
|
585
|
+
else:
|
|
586
|
+
# split_first 无版本布局:{split}/{id}.json(split=None/default 不合
|
|
587
|
+
# 此形态,调用方须传真实 split 名)
|
|
588
|
+
l1, l2 = P.split_dataset(dataset)
|
|
589
|
+
base = f"{oss_prefix}/{l1}/{l2}"
|
|
590
|
+
key = (f"{base}/{split}/{instance_id}.json"
|
|
591
|
+
if split and split != "default"
|
|
592
|
+
else f"{base}/{instance_id}.json")
|
|
593
|
+
|
|
594
|
+
# 存在性判定用 LIST(list_only=True 仅需 ListObjects)而非 HEAD——
|
|
537
595
|
# list-only STS 无 oss:GetObject,object_exists 的 HEAD 会在 STS 签发或
|
|
538
596
|
# OSS 侧被拒,导致已存在的实例恒被误判为不存在。
|
|
539
597
|
try:
|
|
540
|
-
keys = content_store.list_objects(key,
|
|
598
|
+
keys = content_store.list_objects(key, list_only=True)
|
|
541
599
|
except Exception: # noqa: BLE001
|
|
542
600
|
return None
|
|
543
601
|
|
|
@@ -551,11 +609,12 @@ def build_synthetic_instance(
|
|
|
551
609
|
split=split or "default",
|
|
552
610
|
registered=False,
|
|
553
611
|
)
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
612
|
+
if read_content:
|
|
613
|
+
data = _read_json_object(content_store, key)
|
|
614
|
+
if data:
|
|
615
|
+
inst.tags = data.get("tags", {})
|
|
616
|
+
inst.difficulty = data.get("difficulty")
|
|
617
|
+
inst.docker_image = data.get("docker_image")
|
|
618
|
+
inst.extend_meta = data.get("extend_meta", {})
|
|
560
619
|
|
|
561
620
|
return inst
|
|
@@ -326,20 +326,23 @@ class Transport:
|
|
|
326
326
|
duration_seconds: int | None = None,
|
|
327
327
|
dataset_id: str | None = None,
|
|
328
328
|
dataset_name: str | None = None,
|
|
329
|
-
permission: str | None = None
|
|
329
|
+
permission: str | None = None,
|
|
330
|
+
list_only: bool = False) -> dict:
|
|
330
331
|
"""换 OSS STS 三元组(收窄到 bucket+prefix)。返回归一化小写字段。
|
|
331
332
|
|
|
332
|
-
同 (bucket,prefix,duration,dataset,permission)
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
同 (bucket,prefix,duration,dataset,permission,list_only) 在有效期内复用缓存,
|
|
334
|
+
把 N 次签发降到 ~1 次。dataset_id / dataset_name 至少传一个(细粒度授权场景),
|
|
335
|
+
二者可并存,服务端校验一致性。
|
|
335
336
|
|
|
336
|
-
``permission``(可选,admin/writer/reader
|
|
337
|
+
``permission``(可选,admin/writer/reader)是**权限封顶**:apiserver 取
|
|
337
338
|
``min(调用方真实角色, permission)``,**只能收窄不能提权**——传 admin 不会把
|
|
338
339
|
reader 变成 admin。用途是最小权限原则(只读任务显式请求 reader,即便自己是
|
|
339
340
|
admin),以及避免把可写令牌交给下游只读组件。admin 与 writer 在 STS 策略层等价,
|
|
340
|
-
真正有区别的是 reader(只给 GetObject
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
真正有区别的是 reader(只给 GetObject)。ACR 分支不支持该字段(镜像令牌是实例级的)。
|
|
342
|
+
|
|
343
|
+
``list_only`` 是**发现模式开关**(与 permission 封顶正交):True 时签发仅含
|
|
344
|
+
oss:ListObjects + oss:GetBucketInfo 的令牌,跳过 DatasetRBAC 检查,用于未注册
|
|
345
|
+
dataset 的 OSS 回退目录探测。二者可并存(list_only 分支优先)。
|
|
343
346
|
"""
|
|
344
347
|
# 空串与 None 一律视为"不封顶"(与 Go 的 Permission == "" 语义对齐)。
|
|
345
348
|
if permission and permission not in PERMISSION_VALUES:
|
|
@@ -358,6 +361,8 @@ class Transport:
|
|
|
358
361
|
body["dataset_name"] = dataset_name
|
|
359
362
|
if permission:
|
|
360
363
|
body["permission"] = permission
|
|
364
|
+
if list_only:
|
|
365
|
+
body["list_only"] = True
|
|
361
366
|
r = self.post("/apis/v1/credentials", body, idempotent=True)
|
|
362
367
|
return {
|
|
363
368
|
"access_key_id": self._require(r, "OSS_ACCESS_KEY_ID"),
|
|
@@ -371,10 +376,11 @@ class Transport:
|
|
|
371
376
|
}
|
|
372
377
|
|
|
373
378
|
ttl = float(min(duration_seconds or MIN_STS_DURATION, MAX_STS_DURATION))
|
|
374
|
-
# permission
|
|
375
|
-
#
|
|
379
|
+
# permission 与 list_only 都必须进缓存 key:否则请求 reader 封顶的调用方可能
|
|
380
|
+
# 命中此前那份未封顶(可写)的缓存令牌,权限收窄被静默绕过;list-only 令牌
|
|
381
|
+
# 也可能被普通请求复用(反之亦然),发现模式与读写模式互相污染。
|
|
376
382
|
cache_key = ("oss", bucket, prefix, duration_seconds,
|
|
377
|
-
dataset_id, dataset_name, permission or None)
|
|
383
|
+
dataset_id, dataset_name, permission or None, list_only)
|
|
378
384
|
return self._cached_cred(cache_key, sign, fallback_ttl=ttl)
|
|
379
385
|
|
|
380
386
|
def get_acr_credentials(self, acr_registry_url: str, *,
|
|
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
|
|
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
|
|
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
|
{instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{instance_repo-1.3.2.dev1 → instance_repo-1.3.2.dev3}/instance_repo.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|