instance-repo 1.3.5.dev1__tar.gz → 1.3.5.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.
Files changed (49) hide show
  1. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/PKG-INFO +1 -1
  2. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/__init__.py +1 -1
  3. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/instances.py +91 -6
  4. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/transport.py +20 -7
  5. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo.egg-info/PKG-INFO +1 -1
  6. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/pyproject.toml +1 -1
  7. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/README.md +0 -0
  8. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/_routing.py +0 -0
  9. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/_site_defaults.py +0 -0
  10. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/artifacts.py +0 -0
  11. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/bootstrap.py +0 -0
  12. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/cache.py +0 -0
  13. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/cli.py +0 -0
  14. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/__init__.py +0 -0
  15. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/benchmarks.py +0 -0
  16. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/config.py +0 -0
  17. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/datasets.py +0 -0
  18. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/images.py +0 -0
  19. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/reports.py +0 -0
  20. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/scaffold.py +0 -0
  21. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/splits.py +0 -0
  22. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/versions.py +0 -0
  23. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/clients/workflows.py +0 -0
  24. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/concurrency.py +0 -0
  25. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/content.py +0 -0
  26. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/endpoints.py +0 -0
  27. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/errors.py +0 -0
  28. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/image.py +0 -0
  29. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/layout.py +0 -0
  30. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/loader.py +0 -0
  31. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/models.py +0 -0
  32. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/paths.py +0 -0
  33. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/release.py +0 -0
  34. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/repo.py +0 -0
  35. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/retry.py +0 -0
  36. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/store/__init__.py +0 -0
  37. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/store/acr.py +0 -0
  38. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/store/base.py +0 -0
  39. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/store/oss.py +0 -0
  40. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/store/oss_discovery.py +0 -0
  41. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/store/registry.py +0 -0
  42. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/tasktoml.py +0 -0
  43. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo/validate.py +0 -0
  44. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo.egg-info/SOURCES.txt +0 -0
  45. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo.egg-info/dependency_links.txt +0 -0
  46. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo.egg-info/entry_points.txt +0 -0
  47. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo.egg-info/requires.txt +0 -0
  48. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/instance_repo.egg-info/top_level.txt +0 -0
  49. {instance_repo-1.3.5.dev1 → instance_repo-1.3.5.dev3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: instance-repo
3
- Version: 1.3.5.dev1
3
+ Version: 1.3.5.dev3
4
4
  Summary: InstanceRepo SDK — 评测 Instance 统一存储客户端(SDK-only;控制面由 apiserver 承载)
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -20,7 +20,7 @@ from .errors import (
20
20
  TransportError,
21
21
  )
22
22
 
23
- __version__ = "1.3.5.dev1"
23
+ __version__ = "1.3.5.dev3"
24
24
 
25
25
  __all__ = [
26
26
  "Repo", "load_instances",
@@ -1175,6 +1175,7 @@ class InstancesClient:
1175
1175
  validate_instance_id(instance_id, source="pull()")
1176
1176
  expected = None
1177
1177
  fmt = None # None = 未确定,需要探测
1178
+ dir_manifest = None # manifest 中的 directory 条目(如有)
1178
1179
  lay = self._layout()
1179
1180
  # 已下载的元数据字节(探测阶段下载一次,swe/custom 路径复用,避免二次网络往返)。
1180
1181
  meta_bytes: bytes | None = None
@@ -1192,15 +1193,27 @@ class InstancesClient:
1192
1193
  raise
1193
1194
  if meta is not None and meta.manifest:
1194
1195
  has_tgz = False
1196
+ has_dir = False
1195
1197
  for m in meta.manifest:
1196
1198
  if m.content_format == "tgz":
1197
1199
  expected = m.content_digest
1198
1200
  has_tgz = True
1199
1201
  break
1202
+ if m.content_type == "directory":
1203
+ has_dir = True
1204
+ dir_manifest = m
1200
1205
  if has_tgz:
1201
1206
  fmt = "harbor"
1207
+ elif has_dir:
1208
+ # manifest 同时有 json 和 directory 条目 → custom 格式
1209
+ # (directory 条目指向 assets 目录,需逐文件下载)。
1210
+ fmt = "custom"
1211
+ for m in meta.manifest:
1212
+ if m.content_format == "json":
1213
+ expected = m.content_digest
1214
+ break
1202
1215
  else:
1203
- # manifest 有 json entry 但无 tgz → swe 或 custom(需进一步探测)。
1216
+ # manifest 有 json entry 但无 tgz 也无 directory → swe 或 custom(需进一步探测)。
1204
1217
  # custom 推的 manifest 由 ingest 扫描生成,与 swe 同形态;
1205
1218
  # 唯一区分手段是读 metadata JSON 检查 metadata_model 字段。
1206
1219
  for m in meta.manifest:
@@ -1289,6 +1302,39 @@ class InstancesClient:
1289
1302
  if isinstance(meta_doc, dict) and isinstance(
1290
1303
  meta_doc.get("assets"), list):
1291
1304
  asset_rels = [str(x) for x in meta_doc["assets"]]
1305
+ # manifest 有 directory 条目但 JSON 无 assets 字段时,
1306
+ # 用控制面 list_objects API 递归枚举目录下的全部文件作为 asset_rels。
1307
+ if asset_rels is None and dir_manifest is not None:
1308
+ try:
1309
+ env = getattr(self._p, "dataset_environment", None)
1310
+ mm = getattr(self._p, "metadata_model", None)
1311
+ # 递归列举:从根开始,遇到子目录继续深入
1312
+ asset_rels = []
1313
+ stack: list[str | None] = [None] # None = 实例资产根
1314
+ # list_objects 返回的 f.path 包含完整前缀
1315
+ # (如 test-assets/105-creep-forecast/solution/solve.py),
1316
+ # 需要去掉 {split}-assets/{instance_id}/ 前缀得到相对路径。
1317
+ split_str = (split or "default").strip()
1318
+ assets_root = f"{split_str}-assets/{instance_id.strip()}/"
1319
+ while stack:
1320
+ cur_path = stack.pop()
1321
+ objs = self.list_objects(
1322
+ dataset, instance_id, split=split,
1323
+ version=version, path=cur_path,
1324
+ environment=env, metadata_model=mm)
1325
+ for f in objs.files:
1326
+ rel = f.path
1327
+ if rel.startswith(assets_root):
1328
+ rel = rel[len(assets_root):]
1329
+ asset_rels.append(rel)
1330
+ for d in objs.directories:
1331
+ # directory path 也含完整前缀,去掉后入栈
1332
+ d_path = d.path.rstrip("/")
1333
+ if d_path.startswith(assets_root):
1334
+ d_path = d_path[len(assets_root):]
1335
+ stack.append(d_path)
1336
+ except Exception: # noqa: BLE001
1337
+ asset_rels = None # 枚举失败回退到 OSS LIST
1292
1338
  self._download_custom_assets(
1293
1339
  dataset, instance_id, version, split, dest_dir, lay, sts_prefix,
1294
1340
  asset_rels=asset_rels, path=path)
@@ -1300,11 +1346,50 @@ class InstancesClient:
1300
1346
  # 已在探测阶段下载,直接写入本地。
1301
1347
  dest.parent.mkdir(parents=True, exist_ok=True)
1302
1348
  dest.write_bytes(meta_bytes)
1303
- return dest
1304
- return self._content.get_object(
1305
- idx_key, dest, expected_digest=expected,
1306
- sts_prefix=sts_prefix,
1307
- dataset=dataset)
1349
+ else:
1350
+ self._content.get_object(
1351
+ idx_key, dest, expected_digest=expected,
1352
+ sts_prefix=sts_prefix,
1353
+ dataset=dataset)
1354
+ # swe 格式也可能附带 assets 目录(JSON + directory 混合格式)。
1355
+ # 尝试用 list_objects 检查并下载目录内容。
1356
+ try:
1357
+ objs = self.list_objects(
1358
+ dataset, instance_id, split=split,
1359
+ version=version,
1360
+ environment=getattr(self._p, "dataset_environment", None),
1361
+ metadata_model=getattr(self._p, "metadata_model", None))
1362
+ if objs.files or objs.directories:
1363
+ # 有 assets 内容 → 递归枚举并下载
1364
+ asset_rels: list[str] = []
1365
+ stack: list[str | None] = [None]
1366
+ split_str = (split or "default").strip()
1367
+ assets_root = f"{split_str}-assets/{instance_id.strip()}/"
1368
+ while stack:
1369
+ cur = stack.pop()
1370
+ page = self.list_objects(
1371
+ dataset, instance_id, split=split,
1372
+ version=version, path=cur,
1373
+ environment=getattr(self._p, "dataset_environment", None),
1374
+ metadata_model=getattr(self._p, "metadata_model", None))
1375
+ for f in page.files:
1376
+ rel = f.path
1377
+ if rel.startswith(assets_root):
1378
+ rel = rel[len(assets_root):]
1379
+ asset_rels.append(rel)
1380
+ for d in page.directories:
1381
+ d_path = d.path.rstrip("/")
1382
+ if d_path.startswith(assets_root):
1383
+ d_path = d_path[len(assets_root):]
1384
+ stack.append(d_path)
1385
+ if asset_rels:
1386
+ self._download_custom_assets(
1387
+ dataset, instance_id, version, split,
1388
+ dest_dir, lay, sts_prefix,
1389
+ asset_rels=asset_rels, path=path)
1390
+ except Exception: # noqa: BLE001
1391
+ pass # assets 目录不存在或枚举失败 → 仅保留 JSON
1392
+ return dest
1308
1393
 
1309
1394
  # harbor:下载 content.tgz
1310
1395
  dest = Path(dest_dir) / instance_id / "content.tgz"
@@ -327,12 +327,13 @@ class Transport:
327
327
  dataset_id: str | None = None,
328
328
  dataset_name: str | None = None,
329
329
  permission: str | None = None,
330
- list_only: bool = False) -> dict:
330
+ list_only: bool = False,
331
+ dataset_source: str | None = None) -> dict:
331
332
  """换 OSS STS 三元组(收窄到 bucket+prefix)。返回归一化小写字段。
332
333
 
333
- 同 (bucket,prefix,duration,dataset,permission,list_only) 在有效期内复用缓存,
334
- 把 N 次签发降到 ~1 次。dataset_id / dataset_name 至少传一个(细粒度授权场景),
335
- 二者可并存,服务端校验一致性。
334
+ 同 (bucket,prefix,duration,dataset,permission,list_only,dataset_source)
335
+ 在有效期内复用缓存,把 N 次签发降到 ~1 次。dataset_id / dataset_name
336
+ 至少传一个(细粒度授权场景),二者可并存,服务端校验一致性。
336
337
 
337
338
  ``permission``(可选,admin/writer/reader)是**权限封顶**:apiserver 取
338
339
  ``min(调用方真实角色, permission)``,**只能收窄不能提权**——传 admin 不会把
@@ -343,12 +344,20 @@ class Transport:
343
344
  ``list_only`` 是**发现模式开关**(与 permission 封顶正交):True 时签发仅含
344
345
  oss:ListObjects + oss:GetBucketInfo 的令牌,跳过 DatasetRBAC 检查,用于未注册
345
346
  dataset 的 OSS 回退目录探测。二者可并存(list_only 分支优先)。
347
+
348
+ ``dataset_source``(可选,"db"/"oss")指定数据集管理源:
349
+ - 空/"db"(默认):走 MongoDB 注册 + RBAC 授权流程
350
+ - "oss":纯 OSS 模式,跳过 RBAC,dataset_name/dataset_id 可不填,
351
+ 直接按 bucket+prefix 签发 STS
346
352
  """
347
353
  # 空串与 None 一律视为"不封顶"(与 Go 的 Permission == "" 语义对齐)。
348
354
  if permission and permission not in PERMISSION_VALUES:
349
355
  raise SchemaError(
350
356
  f"invalid permission '{permission}'; valid values: "
351
357
  f"{', '.join(PERMISSION_VALUES)}")
358
+ if dataset_source and dataset_source not in ("db", "oss"):
359
+ raise SchemaError(
360
+ f"invalid dataset_source '{dataset_source}'; valid values: db, oss")
352
361
 
353
362
  def sign() -> dict:
354
363
  body: dict[str, Any] = {"resource": "oss",
@@ -363,6 +372,8 @@ class Transport:
363
372
  body["permission"] = permission
364
373
  if list_only:
365
374
  body["list_only"] = True
375
+ if dataset_source:
376
+ body["dataset_source"] = dataset_source
366
377
  r = self.post("/apis/v1/credentials", body, idempotent=True)
367
378
  return {
368
379
  "access_key_id": self._require(r, "OSS_ACCESS_KEY_ID"),
@@ -376,11 +387,13 @@ class Transport:
376
387
  }
377
388
 
378
389
  ttl = float(min(duration_seconds or MIN_STS_DURATION, MAX_STS_DURATION))
379
- # permission 与 list_only 都必须进缓存 key:否则请求 reader 封顶的调用方可能
390
+ # permission、list_only、dataset_source 都必须进缓存 key:否则请求 reader 封顶的调用方可能
380
391
  # 命中此前那份未封顶(可写)的缓存令牌,权限收窄被静默绕过;list-only 令牌
381
- # 也可能被普通请求复用(反之亦然),发现模式与读写模式互相污染。
392
+ # 也可能被普通请求复用(反之亦然),发现模式与读写模式互相污染;
393
+ # oss 模式凭据不能被 db 模式复用(反之亦然)。
382
394
  cache_key = ("oss", bucket, prefix, duration_seconds,
383
- dataset_id, dataset_name, permission or None, list_only)
395
+ dataset_id, dataset_name, permission or None, list_only,
396
+ dataset_source or None)
384
397
  return self._cached_cred(cache_key, sign, fallback_ttl=ttl)
385
398
 
386
399
  def get_acr_credentials(self, acr_registry_url: str, *,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: instance-repo
3
- Version: 1.3.5.dev1
3
+ Version: 1.3.5.dev3
4
4
  Summary: InstanceRepo SDK — 评测 Instance 统一存储客户端(SDK-only;控制面由 apiserver 承载)
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "instance-repo"
7
- version = "1.3.5.dev1"
7
+ version = "1.3.5.dev3"
8
8
  description = "InstanceRepo SDK — 评测 Instance 统一存储客户端(SDK-only;控制面由 apiserver 承载)"
9
9
  requires-python = ">=3.10"
10
10
  readme = "README.md"